diff --git a/byteplus/cdn/cdn_certificate/data_source_byteplus_cdn_certificates.go b/byteplus/cdn/cdn_certificate/data_source_byteplus_cdn_certificates.go new file mode 100644 index 0000000..7151477 --- /dev/null +++ b/byteplus/cdn/cdn_certificate/data_source_byteplus_cdn_certificates.go @@ -0,0 +1,144 @@ +package cdn_certificate + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnCertificates() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnCertificatesRead, + Schema: map[string]*schema.Schema{ + "cert_id": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates a certificate ID to retrieve the certificate with that ID.", + }, + "configured_domain": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Indicates a list of domain names for acceleration, to obtain certificates that have been bound to any domain name on the list.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates a domain name used to obtain certificates that include that domain name in the SAN field. The domain name can be a wildcard domain. For example, *.example.com can match certificates containing img.example.com or www.example.com, etc., in the SAN field.", + }, + "fuzzy_match": { + Type: schema.TypeBool, + Optional: true, + Description: "When Name is specified, FuzzyMatch indicates the matching method used by the CDN when filtering certificates by Name. The parameter can have the following values:\ntrue: indicates fuzzy matching.\nfalse: indicates exact matching.\nIf you don not specify Name, FuzzyMatch is not effective.\nThe default value of FuzzyMatch is false.", + }, + "status": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates a list of states to retrieve certificates that are in any of the states on the list. The parameter can have the following values:\nrunning: indicates certificates with a remaining validity period of more than 30 days.\nexpired: indicates certificates that have expired.\nexpiring_soon: indicates certificates with a remaining validity period of 30 days or less but have not yet expired.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "certificates": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of the certificate.", + }, + "cert_id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of the certificate.", + }, + "source": { + Type: schema.TypeString, + Computed: true, + Description: "The source of the certificate.", + }, + "cert_name": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the content of the Common Name (CN) field of the certificate.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the certificate. The parameter can have the following values:\nrunning: indicates the certificate has a remaining validity period of more than 30 days.\nexpired: indicates the certificate has expired.\nexpiring_soon: indicates the certificate has a remaining validity period of 30 days or less but has not yet expired.", + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the domain names in the SAN field of the certificate.", + }, + "desc": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the remark of the certificate.", + }, + "configured_domain": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the list of domain names associated with the certificate. If the certificate has not been associated with any domain name, the parameter value is null.", + }, + "effective_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the issuance time of the certificate. The unit is Unix timestamp.", + }, + "expire_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the expiration time of the certificate. The unit is Unix timestamp.", + }, + "cert_fingerprint": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the fingerprint information of the certificate.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sha1": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates a fingerprint based on the SHA-1 encryption algorithm, composed of 40 hexadecimal characters.", + }, + "sha256": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates a fingerprint based on the SHA-256 encryption algorithm, composed of 64 hexadecimal characters.", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnCertificatesRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnCertificateService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnCertificates()) +} diff --git a/byteplus/cdn/cdn_certificate/resource_byteplus_cdn_certificate.go b/byteplus/cdn/cdn_certificate/resource_byteplus_cdn_certificate.go new file mode 100644 index 0000000..2c9b16b --- /dev/null +++ b/byteplus/cdn/cdn_certificate/resource_byteplus_cdn_certificate.go @@ -0,0 +1,159 @@ +package cdn_certificate + +import ( + "fmt" + "log" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnCertificate can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_certificate.default resource_id +``` + +*/ + +func ResourceByteplusCdnCertificate() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnCertificateCreate, + Read: resourceByteplusCdnCertificateRead, + Delete: resourceByteplusCdnCertificateDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "certificate": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Indicates the content of the certificate file, which must include the complete certificate chain. The line breaks in the content should be replaced with \\r\\n. The certificate file must have an extension of either `.crt` or `.pem`.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + "private_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Indicates the content of the certificate private key file. The line breaks in the content should be replaced with \\r\\n. The certificate private key file must have an extension of either `.key` or `.pem`.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + "desc": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Indicates the remarks for the certificate.", + }, + "repeatable": { + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + Description: "Indicates whether uploading the same certificate is allowed. If the fingerprints of two certificates are the same, these certificates are considered identical. This parameter can take the following values:\n\ntrue: Allows the upload of the same certificate.\nfalse: Does not allow the upload of the same certificate. When calling this API, the CDN will check for the existence of an identical certificate. If one exists, you will not be able to upload the certificate, and the Error structure in the response body will include the ID of the existing certificate.\nThe default value of this parameter is true.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + + // computed fields + "source": { + Type: schema.TypeString, + Computed: true, + Description: "The source of the certificate.", + }, + "cert_name": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the content of the Common Name (CN) field of the certificate.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the certificate. The parameter can have the following values:\nrunning: indicates the certificate has a remaining validity period of more than 30 days.\nexpired: indicates the certificate has expired.\nexpiring_soon: indicates the certificate has a remaining validity period of 30 days or less but has not yet expired.", + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the domain names in the SAN field of the certificate.", + }, + "configured_domain": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the list of domain names associated with the certificate. If the certificate has not been associated with any domain name, the parameter value is null.", + }, + "effective_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the issuance time of the certificate. The unit is Unix timestamp.", + }, + "expire_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the expiration time of the certificate. The unit is Unix timestamp.", + }, + "cert_fingerprint": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the fingerprint information of the certificate.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sha1": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates a fingerprint based on the SHA-1 encryption algorithm, composed of 40 hexadecimal characters.", + }, + "sha256": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates a fingerprint based on the SHA-256 encryption algorithm, composed of 64 hexadecimal characters.", + }, + }, + }, + }, + }, + } + return resource +} + +func resourceByteplusCdnCertificateCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCertificateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnCertificate()) + if err != nil { + return fmt.Errorf("error on creating cdn_certificate %q, %s", d.Id(), err) + } + return resourceByteplusCdnCertificateRead(d, meta) +} + +func resourceByteplusCdnCertificateRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCertificateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnCertificate()) + if err != nil { + return fmt.Errorf("error on reading cdn_certificate %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnCertificateUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCertificateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnCertificate()) + if err != nil { + return fmt.Errorf("error on updating cdn_certificate %q, %s", d.Id(), err) + } + return resourceByteplusCdnCertificateRead(d, meta) +} + +func resourceByteplusCdnCertificateDelete(d *schema.ResourceData, meta interface{}) (err error) { + log.Printf("[DEBUG] deleting a byteplus_cdn_certificate resource will only remove the cdn certificate from terraform state.") + service := NewCdnCertificateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnCertificate()) + if err != nil { + return fmt.Errorf("error on deleting cdn_certificate %q, %s", d.Id(), err) + } + return err +} diff --git a/byteplus/cdn/cdn_certificate/service_byteplus_cdn_certificate.go b/byteplus/cdn/cdn_certificate/service_byteplus_cdn_certificate.go new file mode 100644 index 0000000..294fdad --- /dev/null +++ b/byteplus/cdn/cdn_certificate/service_byteplus_cdn_certificate.go @@ -0,0 +1,247 @@ +package cdn_certificate + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/mitchellh/copystructure" +) + +type ByteplusCdnCertificateService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnCertificateService(c *bp.SdkClient) *ByteplusCdnCertificateService { + return &ByteplusCdnCertificateService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnCertificateService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnCertificateService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + newCondition map[string]interface{} + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageNumberQuery(m, "PageSize", "PageNumber", 100, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListCertInfo" + + deepCopyValue, err := copystructure.Copy(condition) + if err != nil { + return data, fmt.Errorf(" DeepCopy condition error: %v ", err) + } + if newCondition, ok = deepCopyValue.(map[string]interface{}); !ok { + return data, fmt.Errorf(" DeepCopy condition error: newCondition is not map ") + } + + // 处理 ConfiguredDomain,逗号分离 + if ids, exists := condition["ConfiguredDomain"]; exists { + idsArr, ok := ids.([]interface{}) + if !ok { + return data, fmt.Errorf(" ConfiguredDomain is not slice ") + } + configuredDomains := make([]string, 0) + for _, id := range idsArr { + configuredDomains = append(configuredDomains, id.(string)) + } + newCondition["ConfiguredDomain"] = strings.Join(configuredDomains, ",") + } + newCondition["Source"] = "cert_center" + + bytes, _ := json.Marshal(newCondition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if newCondition == nil { + resp, err = s.Client.UniversalClient.DoCall(getPostUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getPostUniversalInfo(action), &newCondition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, newCondition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.CertInfo", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.CertInfo is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnCertificateService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + req := map[string]interface{}{ + "CertId": id, + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_certificate %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnCertificateService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + failStates []string + ) + failStates = append(failStates, "Failed") + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + for _, v := range failStates { + if v == status.(string) { + return nil, "", fmt.Errorf("cdn_certificate status error, status: %s", status.(string)) + } + } + return d, status.(string), err + }, + } +} + +func (ByteplusCdnCertificateService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnCertificateService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "AddCertificate", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "repeatable": { + TargetField: "Repeatable", + ForceGet: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["Source"] = "cert_center" + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + id, _ := bp.ObtainSdkValue("Result.CertId", *resp) + d.SetId(id.(string)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"running"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCertificateService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnCertificateService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnCertificateService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + RequestConverts: map[string]bp.RequestConvert{ + "configured_domain": { + TargetField: "ConfiguredDomain", + ConvertType: bp.ConvertJsonArray, + }, + }, + IdField: "CertId", + CollectField: "certificates", + ContentType: bp.ContentTypeJson, + ResponseConverts: map[string]bp.ResponseConvert{ + "CertId": { + TargetField: "id", + KeepDefault: true, + }, + }, + } +} + +func (s *ByteplusCdnCertificateService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_cipher_template/data_source_byteplus_cdn_cipher_templates.go b/byteplus/cdn/cdn_cipher_template/data_source_byteplus_cdn_cipher_templates.go new file mode 100644 index 0000000..3d20951 --- /dev/null +++ b/byteplus/cdn/cdn_cipher_template/data_source_byteplus_cdn_cipher_templates.go @@ -0,0 +1,323 @@ +package cdn_cipher_template + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnCipherTemplates() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnCipherTemplatesRead, + Schema: map[string]*schema.Schema{ + "filters": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fuzzy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Indicates the matching method. This parameter can take the following values: " + + "true: Indicates fuzzy matching. A policy is considered to meet the filtering criteria if the corresponding value of Name contains any value in the Value array. " + + "false: Indicates exact matching. A policy is considered to meet the filtering criteria if the corresponding value of Name matches any value in the Value array. " + + "Moreover, the Fuzzy value you can specify is affected by the Name value. See the description of Name. " + + "The default value of this parameter is false. " + + "Note that the matching process is case-sensitive.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Represents the filtering type. This parameter can take the following values: " + + "Title: Filters policies by name. " + + "Id: Filters policies by ID. For this parameter value, the value of Fuzzy can only be false. " + + "Domain: Filters policies by the bound domain name. " + + "Type: Filters policies by type. For this parameter value, the value of Fuzzy can only be false. " + + "Status: Filters policies by status. For this parameter value, the value of Fuzzy can only be false. " + + "You can specify multiple filtering criteria simultaneously, but the Name in different filtering criteria cannot be the same.", + }, + "value": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Represents the values corresponding to Name, which is an array. " + + "When Name is Title, Id, or Domain, each value in the Value array should not exceed 100 characters in length. " + + "When Name is Type, the Value array can include one or more of the following values: " + + "cipher: Indicates a encryption policy. " + + "service: Indicates a delivery policy. " + + "When Name is Status, the Value array can include one or more of the following values: " + + "locked: Indicates the status is \"published\". " + + "editing: Indicates the status is \"draft\". " + + "When Fuzzy is false, you can specify multiple values in the array. " + + "When Fuzzy is true, you can only specify one value in the array.", + }, + }, + }, + Description: "Indicates a set of filtering criteria used to obtain a list of policies that meet these criteria. " + + "If you do not specify any filtering criteria, this API returns all policies under your account. " + + "Multiple filtering criteria are related by AND, meaning only policies that meet all filtering criteria will be included in the list returned by this API. " + + "In the API response, the actual policies returned are affected by PageNum and PageSize.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "templates": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bound_domains": { + Type: schema.TypeList, + Computed: true, + Description: "Represents a list of domain names bound to the policy specified by TemplateId. " + + "If the policy is not bound to any domain names, the value of this parameter is null.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bound_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the time when the policy was bound to the domain name specified by Domain, in Unix timestamp format.", + }, + "domain": { + Type: schema.TypeString, + Computed: true, + Description: "Represents one of the domain names bound to the policy.", + }, + }, + }, + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the creation time of the policy, in Unix timestamp format.", + }, + "message": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the description of the policy.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the policy. This parameter can take the following values: " + + "locked: Indicates the status is \"published\". " + + "editing: Indicates the status is \"draft\".", + }, + "template_id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of a policy in the list of policies returned by the API.", + }, + "title": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the name of the policy.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the type of the policy. This parameter can take the following values: " + + "cipher: Indicates an encryption policy. " + + "service: Indicates a distribution policy.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the last modification time of the policy, in Unix timestamp format. " + + "If the policy has not been updated since its creation, the value of this parameter is the same as CreateTime.", + }, + "exception": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether the policy includes special configurations. " + + "Special configurations refer to those not operated by users but by BytePlus engineers. " + + "This parameter can take the following values:" + + " true: Indicates it includes special configurations. " + + "false: Indicates it does not include special configurations.", + }, + "project": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the project to which the policy belongs.", + }, + + "https": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the configuration module for the HTTPS encryption service.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disable_http": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether the CDN accepts HTTP user requests. " + + "This parameter can take the following values: " + + "true: Indicates that it does not accept. If an HTTP request is received, the CDN will reject the request. " + + "false: Indicates that it accepts. The default value for this parameter is false.", + }, + "forced_redirect": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the configuration for the mandatory redirection from HTTP to HTTPS. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_forced_redirect": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates the switch for the Forced Redirect configuration. " + + "This parameter can take the following values: " + + "true: Indicates to enable Forced Redirect. " + + "false: Indicates to disable Forced Redirect.", + }, + "status_code": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status code returned to the client by the CDN when forced redirect occurs. " + + "This parameter can take the following values: " + + "301: Indicates that the returned status code is 301. " + + "302: Indicates that the returned status code is 302. " + + "The default value for this parameter is 301.", + }, + }, + }, + }, + "http2": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates the switch for HTTP/2 configuration. " + + "This parameter can take the following values: " + + "true: Indicates to enable HTTP/2. " + + "false: Indicates to disable HTTP/2. " + + "The default value for this parameter is true.", + }, + "ocsp": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether to enable OCSP Stapling. " + + "This parameter can take the following values: " + + "true: Indicates to enable OCSP Stapling. " + + "false: Indicates to disable OCSP Stapling. " + + "The default value for this parameter is false.", + }, + "tls_version": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Indicates a list that specifies the TLS versions supported by the domain name. " + + "This parameter can take the following values: " + + "tlsv1.0: Indicates TLS 1.0. " + + "tlsv1.1: Indicates TLS 1.1. " + + "tlsv1.2: Indicates TLS 1.2. " + + "tlsv1.3: Indicates TLS 1.3. " + + "The default value for this parameter is [\"tlsv1.1\", \"tlsv1.2\", \"tlsv1.3\"].", + }, + "hsts": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the HSTS (HTTP Strict Transport Security) configuration module. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subdomain": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates whether the HSTS configuration should also be applied to the subdomains of the domain name. " + + "This parameter can take the following values: " + + "include: Indicates that HSTS settings apply to subdomains. " + + "exclude: Indicates that HSTS settings do not apply to subdomains. " + + "The default value for this parameter is exclude.", + }, + "switch": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether to enable HSTS. " + + "This parameter can take the following values: " + + "true: Indicates to enable HSTS. " + + "false: Indicates to disable HSTS. " + + "The default value for this parameter is false.", + }, + "ttl": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the expiration time for the Strict-Transport-Security response header in the browser cache, in seconds. " + + "If Switch is true, this parameter is required. " + + "The value range for this parameter is 0 - 31,536,000 seconds, where 31,536,000 seconds represents 365 days. " + + "If the value of this parameter is 0, it is equivalent to disabling the HSTS settings.", + }, + }, + }, + }, + }, + }, + }, + + "http_forced_redirect": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the configuration module for the forced redirection from HTTPS to HTTP. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_forced_redirect": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether to enable the forced redirection from HTTPS. " + + "This parameter can take the following values: " + + "true: Indicates to enable the forced redirection from HTTPS. " + + "Once enabled, the content delivery network will respond with StatusCode to inform the browser to send an HTTPS request when it receives an HTTP request from a user. " + + "false: Indicates to disable the forced redirection from HTTPS.", + }, + "status_code": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status code returned by the content delivery network when forced redirection from HTTPS occurs. " + + "The default value for this parameter is 301.", + }, + }, + }, + }, + "quic": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the QUIC configuration module. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "switch": { + Computed: true, + Type: schema.TypeBool, + Description: "Indicates whether to enable QUIC. " + + "This parameter can take the following values: " + + "true: Indicates to enable QUIC. " + + "false: Indicates to disable QUIC.", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnCipherTemplatesRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnCipherTemplateService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnCipherTemplates()) +} diff --git a/byteplus/cdn/cdn_cipher_template/resource_byteplus_cdn_cipher_template.go b/byteplus/cdn/cdn_cipher_template/resource_byteplus_cdn_cipher_template.go new file mode 100644 index 0000000..0641259 --- /dev/null +++ b/byteplus/cdn/cdn_cipher_template/resource_byteplus_cdn_cipher_template.go @@ -0,0 +1,264 @@ +package cdn_cipher_template + +import ( + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnCipherTemplate can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_cipher_template.default resource_id +``` + +*/ + +func ResourceByteplusCdnCipherTemplate() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnCipherTemplateCreate, + Read: resourceByteplusCdnCipherTemplateRead, + Update: resourceByteplusCdnCipherTemplateUpdate, + Delete: resourceByteplusCdnCipherTemplateDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + Description: "Indicates the name of the encryption policy you want to create. The name must not exceed 100 characters.", + }, + "message": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates the description of the encryption policy, which must not exceed 120 characters.", + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates the project to which this encryption policy belongs. The default value of the parameter is default, indicating the Default project.", + }, + "https": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: "Indicates the configuration module for the HTTPS encryption service.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disable_http": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates whether the CDN accepts HTTP user requests. " + + "This parameter can take the following values: " + + "true: Indicates that it does not accept. If an HTTP request is received, the CDN will reject the request. " + + "false: Indicates that it accepts. The default value for this parameter is false.", + }, + "forced_redirect": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"http_forced_redirect"}, + Description: "Indicates the configuration for the mandatory redirection from HTTP to HTTPS. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_forced_redirect": { + Type: schema.TypeBool, + Required: true, + Description: "Indicates the switch for the Forced Redirect configuration. " + + "This parameter can take the following values: " + + "true: Indicates to enable Forced Redirect. " + + "false: Indicates to disable Forced Redirect.", + }, + "status_code": { + Type: schema.TypeString, + Required: true, + Description: "Indicates the status code returned to the client by the CDN when forced redirect occurs. " + + "This parameter can take the following values: " + + "301: Indicates that the returned status code is 301. " + + "302: Indicates that the returned status code is 302. " + + "The default value for this parameter is 301.", + }, + }, + }, + }, + "http2": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates the switch for HTTP/2 configuration. " + + "This parameter can take the following values: " + + "true: Indicates to enable HTTP/2. " + + "false: Indicates to disable HTTP/2. " + + "The default value for this parameter is true.", + }, + "ocsp": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates whether to enable OCSP Stapling. " + + "This parameter can take the following values: " + + "true: Indicates to enable OCSP Stapling. " + + "false: Indicates to disable OCSP Stapling. " + + "The default value for this parameter is false.", + }, + "tls_version": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Indicates a list that specifies the TLS versions supported by the domain name. " + + "This parameter can take the following values: " + + "tlsv1.0: Indicates TLS 1.0. " + + "tlsv1.1: Indicates TLS 1.1. " + + "tlsv1.2: Indicates TLS 1.2. " + + "tlsv1.3: Indicates TLS 1.3. " + + "The default value for this parameter is [\"tlsv1.1\", \"tlsv1.2\", \"tlsv1.3\"].", + }, + "hsts": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: "Indicates the HSTS (HTTP Strict Transport Security) configuration module. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subdomain": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates whether the HSTS configuration should also be applied to the subdomains of the domain name. " + + "This parameter can take the following values: " + + "include: Indicates that HSTS settings apply to subdomains. " + + "exclude: Indicates that HSTS settings do not apply to subdomains. " + + "The default value for this parameter is exclude.", + }, + "switch": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates whether to enable HSTS. " + + "This parameter can take the following values: " + + "true: Indicates to enable HSTS. " + + "false: Indicates to disable HSTS. " + + "The default value for this parameter is false.", + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + Description: "Indicates the expiration time for the Strict-Transport-Security response header in the browser cache, in seconds. " + + "If Switch is true, this parameter is required. " + + "The value range for this parameter is 0 - 31,536,000 seconds, where 31,536,000 seconds represents 365 days. " + + "If the value of this parameter is 0, it is equivalent to disabling the HSTS settings.", + }, + }, + }, + }, + }, + }, + }, + + "http_forced_redirect": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"https.0.forced_redirect"}, + Description: "Indicates the configuration module for the forced redirection from HTTPS to HTTP. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enable_forced_redirect": { + Type: schema.TypeBool, + Required: true, + Description: "Indicates whether to enable the forced redirection from HTTPS. " + + "This parameter can take the following values: " + + "true: Indicates to enable the forced redirection from HTTPS. " + + "Once enabled, the content delivery network will respond with StatusCode to inform the browser to send an HTTPS request when it receives an HTTP request from a user. " + + "false: Indicates to disable the forced redirection from HTTPS.", + }, + "status_code": { + Type: schema.TypeString, + Required: true, + Description: "Indicates the status code returned by the content delivery network when forced redirection from HTTPS occurs. " + + "The default value for this parameter is 301.", + }, + }, + }, + }, + "quic": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: "Indicates the QUIC configuration module. This feature is disabled by default.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "switch": { + Required: true, + Type: schema.TypeBool, + Description: "Indicates whether to enable QUIC. " + + "This parameter can take the following values: " + + "true: Indicates to enable QUIC. " + + "false: Indicates to disable QUIC.", + }, + }, + }, + }, + "lock_template": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether to lock the template. " + + "If you set this field to true, then the template will be locked. Please note that the template cannot be modified or unlocked after it is locked. " + + "When you want to use this template to create a domain name, please lock the template first. The default value is false.", + }, + }, + } + return resource +} + +func resourceByteplusCdnCipherTemplateCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCipherTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnCipherTemplate()) + if err != nil { + return fmt.Errorf("error on creating cdn_cipher_template %q, %s", d.Id(), err) + } + return resourceByteplusCdnCipherTemplateRead(d, meta) +} + +func resourceByteplusCdnCipherTemplateRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCipherTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnCipherTemplate()) + if err != nil { + return fmt.Errorf("error on reading cdn_cipher_template %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnCipherTemplateUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCipherTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnCipherTemplate()) + if err != nil { + return fmt.Errorf("error on updating cdn_cipher_template %q, %s", d.Id(), err) + } + return resourceByteplusCdnCipherTemplateRead(d, meta) +} + +func resourceByteplusCdnCipherTemplateDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCipherTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnCipherTemplate()) + if err != nil { + return fmt.Errorf("error on deleting cdn_cipher_template %q, %s", d.Id(), err) + } + return err +} diff --git a/byteplus/cdn/cdn_cipher_template/service_byteplus_cdn_cipher_template.go b/byteplus/cdn/cdn_cipher_template/service_byteplus_cdn_cipher_template.go new file mode 100644 index 0000000..2c550e6 --- /dev/null +++ b/byteplus/cdn/cdn_cipher_template/service_byteplus_cdn_cipher_template.go @@ -0,0 +1,540 @@ +package cdn_cipher_template + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnCipherTemplateService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnCipherTemplateService(c *bp.SdkClient) *ByteplusCdnCipherTemplateService { + return &ByteplusCdnCipherTemplateService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnCipherTemplateService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnCipherTemplateService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + data, err = bp.WithPageNumberQuery(m, "PageSize", "PageNum", 100, 1, func(condition map[string]interface{}) ([]interface{}, error) { + + action := "DescribeTemplates" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Templates", *resp) + if err != nil { + return data, err + } + + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Templates is not Slice") + } + + return data, err + }) + if err != nil { + return nil, err + } + + for _, v := range data { + template, ok := v.(map[string]interface{}) + if !ok { + return nil, errors.New("template is not a map") + } + tmpType := template["Type"].(string) + if tmpType != "cipher" { + continue + } + action := "DescribeCipherTemplate" + req := map[string]interface{}{ + "TemplateId": template["TemplateId"], + } + logger.Debug(logger.ReqFormat, action, req) + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &req) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, action, req, *resp) + + https, err := bp.ObtainSdkValue("Result.HTTPS", *resp) + if err != nil { + return data, err + } + + httpsMap, ok := https.(map[string]interface{}) + if ok { + if v, ok = httpsMap["ForcedRedirect"]; ok { + httpsMap["ForcedRedirect"] = []interface{}{v} + } + if v, ok = httpsMap["Hsts"]; ok { + httpsMap["Hsts"] = []interface{}{v} + } + } + + template["HTTPS"] = []interface{}{httpsMap} + + httpForcedRedirect, err := bp.ObtainSdkValue("Result.HttpForcedRedirect", *resp) + if err != nil { + return data, err + } + template["HttpForcedRedirect"] = httpForcedRedirect + + quic, err := bp.ObtainSdkValue("Result.Quic", *resp) + if err != nil { + return data, err + } + template["Quic"] = quic + } + + return data, nil +} + +func (s *ByteplusCdnCipherTemplateService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + filter := map[string]interface{}{ + "Fuzzy": false, + "Name": "Id", + "Value": []string{id}, + } + req := map[string]interface{}{ + "Filters": []interface{}{filter}, + } + + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_cipher_template %s not exist ", id) + } + logger.Debug(logger.RespFormat, "ReadResource Beforce", data) + if quic, ok := resourceData.GetOk("quic"); ok { + if q, ok := data["Quic"]; !ok || q == nil { + data["Quic"] = quic + } + } + status := data["Status"].(string) + if status == "locked" { + data["LockTemplate"] = true + } else if status == "editing" { + data["LockTemplate"] = false + } + return data, err +} + +func (s *ByteplusCdnCipherTemplateService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + failStates []string + ) + failStates = append(failStates, "Failed") + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + for _, v := range failStates { + if v == status.(string) { + return nil, "", fmt.Errorf("cdn_cipher_template status error, status: %s", status.(string)) + } + } + return d, status.(string), err + }, + } +} + +func (s *ByteplusCdnCipherTemplateService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateCipherTemplate", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "lock_template": { + Ignore: true, + }, + "https": { + ConvertType: bp.ConvertJsonObject, + TargetField: "HTTPS", + NextLevelConvert: map[string]bp.RequestConvert{ + "forced_redirect": { + ConvertType: bp.ConvertJsonObject, + TargetField: "ForcedRedirect", + }, + "http2": { + ForceGet: true, + TargetField: "HTTP2", + }, + "ocsp": { + TargetField: "OCSP", + }, + "tls_version": { + TargetField: "TlsVersion", + ConvertType: bp.ConvertJsonArray, + }, + "hsts": { + TargetField: "Hsts", + ConvertType: bp.ConvertJsonObject, + NextLevelConvert: map[string]bp.RequestConvert{ + "subdomain": { + TargetField: "Subdomain", + }, + "switch": { + TargetField: "Switch", + }, + "ttl": { + TargetField: "Ttl", + }, + }, + }, + }, + }, + "http_forced_redirect": { + TargetField: "HttpForcedRedirect", + ConvertType: bp.ConvertJsonObject, + }, + "quic": { + TargetField: "Quic", + ConvertType: bp.ConvertJsonObject, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + id, _ := bp.ObtainSdkValue("Result.TemplateId", *resp) + d.SetId(id.(string)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"editing"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, callback) + if resourceData.Get("lock_template").(bool) { + lockCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "LockTemplate", + ContentType: bp.ContentTypeJson, + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["TemplateId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"locked"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, lockCallback) + } + return callbacks +} + +func (ByteplusCdnCipherTemplateService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, map[string]bp.ResponseConvert{ + "OCSP": { + TargetField: "ocsp", + }, + "HTTP2": { + TargetField: "http2", + }, + "HTTPS": { + TargetField: "https", + }, + }, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnCipherTemplateService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateCipherTemplate", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "project": { + Ignore: true, + }, + "lock_template": { + Ignore: true, + }, + "https": { + TargetField: "HTTPS", + ForceGet: true, + ConvertType: bp.ConvertJsonObject, + NextLevelConvert: map[string]bp.RequestConvert{ + "disable_http": { + TargetField: "DisableHttp", + }, + "forced_redirect": { + TargetField: "ForcedRedirect", + ConvertType: bp.ConvertJsonObject, + NextLevelConvert: map[string]bp.RequestConvert{ + "enable_forced_redirect": { + TargetField: "EnableForcedRedirect", + }, + "status_code": { + TargetField: "StatusCode", + }, + }, + }, + "http2": { + ForceGet: true, + TargetField: "HTTP2", + }, + "hsts": { + TargetField: "Hsts", + ConvertType: bp.ConvertJsonObject, + NextLevelConvert: map[string]bp.RequestConvert{ + "subdomain": { + TargetField: "Subdomain", + }, + "switch": { + TargetField: "Switch", + }, + "ttl": { + TargetField: "Ttl", + }, + }, + }, + "tls_version": { + TargetField: "TlsVersion", + ConvertType: bp.ConvertJsonArray, + }, + }, + }, + "http_forced_redirect": { + TargetField: "HttpForcedRedirect", + ConvertType: bp.ConvertJsonObject, + ForceGet: true, + }, + "message": { + ForceGet: true, + TargetField: "Message", + }, + "title": { + ForceGet: true, + TargetField: "Title", + }, + "quic": { + ForceGet: true, + TargetField: "Quic", + ConvertType: bp.ConvertJsonObject, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if d.HasChanges("https", "http_forced_redirect", "message", "title", "quic") { + (*call.SdkParam)["TemplateId"] = d.Id() + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + }, + } + callbacks = append(callbacks, callback) + if resourceData.HasChange("lock_template") { + lockCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "LockTemplate", + ContentType: bp.ContentTypeJson, + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if !d.Get("lock_template").(bool) { + // 不允许解锁 + return false, fmt.Errorf("Template cannot unlock. ") + } + (*call.SdkParam)["TemplateId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"locked"}, + Timeout: resourceData.Timeout(schema.TimeoutUpdate), + }, + }, + } + callbacks = append(callbacks, lockCallback) + } + return callbacks +} + +func (s *ByteplusCdnCipherTemplateService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteTemplate", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "TemplateId": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading cdn cipher template on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCipherTemplateService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + RequestConverts: map[string]bp.RequestConvert{ + "filters": { + TargetField: "Filters", + ConvertType: bp.ConvertJsonObjectArray, + NextLevelConvert: map[string]bp.RequestConvert{ + "value": { + TargetField: "Value", + ConvertType: bp.ConvertJsonArray, + }, + }, + }, + }, + NameField: "Title", + IdField: "TemplateId", + CollectField: "templates", + ContentType: bp.ContentTypeJson, + ResponseConverts: map[string]bp.ResponseConvert{ + "OCSP": { + TargetField: "ocsp", + }, + "HTTP2": { + TargetField: "http2", + }, + "HTTPS": { + TargetField: "https", + }, + }, + } +} + +func (s *ByteplusCdnCipherTemplateService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} + +func (s *ByteplusCdnCipherTemplateService) ProjectTrn() *bp.ProjectTrn { + return &bp.ProjectTrn{ + ServiceName: "CDN", + ResourceType: "template", + ProjectResponseField: "Project", + ProjectSchemaField: "project", + } +} diff --git a/byteplus/cdn/cdn_cron_job/data_source_byteplus_cdn_cron_jobs.go b/byteplus/cdn/cdn_cron_job/data_source_byteplus_cdn_cron_jobs.go new file mode 100644 index 0000000..f22bf7c --- /dev/null +++ b/byteplus/cdn/cdn_cron_job/data_source_byteplus_cdn_cron_jobs.go @@ -0,0 +1,91 @@ +package cdn_cron_job + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnCronJobs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnCronJobsRead, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + Description: "The id of the function.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "cron_jobs": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "job_name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the cron job.", + }, + "cron_expression": { + Type: schema.TypeString, + Computed: true, + Description: "The cron expression of the cron job.", + }, + "parameter": { + Type: schema.TypeString, + Computed: true, + Description: "The parameter of the cron job.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the cron job.", + }, + "job_state": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the cron job.", + }, + "cron_type": { + Type: schema.TypeInt, + Computed: true, + Description: "The type of the cron job.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The creation time of the cron job. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the cron job. Displayed in UNIX timestamp format.", + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnCronJobsRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnCronJobService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnCronJobs()) +} diff --git a/byteplus/cdn/cdn_cron_job/resource_byteplus_cdn_cron_job.go b/byteplus/cdn/cdn_cron_job/resource_byteplus_cdn_cron_job.go new file mode 100644 index 0000000..187ee14 --- /dev/null +++ b/byteplus/cdn/cdn_cron_job/resource_byteplus_cdn_cron_job.go @@ -0,0 +1,139 @@ +package cdn_cron_job + +import ( + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnCronJob can be imported using the function_id:job_name, e.g. +``` +$ terraform import byteplus_cdn_cron_job.default function_id:job_name +``` + +*/ + +func ResourceByteplusCdnCronJob() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnCronJobCreate, + Read: resourceByteplusCdnCronJobRead, + Update: resourceByteplusCdnCronJobUpdate, + Delete: resourceByteplusCdnCronJobDelete, + Importer: &schema.ResourceImporter{ + State: cronJobImporter, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The id of the function.", + }, + "job_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the cron job. The name must meet the following requirements:\nEach cron job name for a function must be unique\nLength should not exceed 128 characters.", + }, + "cron_expression": { + Type: schema.TypeString, + Required: true, + Description: "The execution expression. The expression must meet the following requirements:\nSupports cron expressions (does not support second-level triggers).", + }, + "cron_type": { + Type: schema.TypeInt, + Required: true, + Description: "The schedule type of the cron job. Possible values:\n1: Global schedule.\n2: Single point schedule.", + }, + "parameter": { + Type: schema.TypeString, + Optional: true, + Description: "The execution parameter of the cron job.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of the cron job.", + }, + + // computed fields + "job_state": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the cron job.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The creation time of the cron job. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the cron job. Displayed in UNIX timestamp format.", + }, + }, + } + return resource +} + +func resourceByteplusCdnCronJobCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnCronJob()) + if err != nil { + return fmt.Errorf("error on creating cdn_cron_job %q, %s", d.Id(), err) + } + return resourceByteplusCdnCronJobRead(d, meta) +} + +func resourceByteplusCdnCronJobRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnCronJob()) + if err != nil { + return fmt.Errorf("error on reading cdn_cron_job %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnCronJobUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnCronJob()) + if err != nil { + return fmt.Errorf("error on updating cdn_cron_job %q, %s", d.Id(), err) + } + return resourceByteplusCdnCronJobRead(d, meta) +} + +func resourceByteplusCdnCronJobDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnCronJob()) + if err != nil { + return fmt.Errorf("error on deleting cdn_cron_job %q, %s", d.Id(), err) + } + return err +} + +var cronJobImporter = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 2 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("function_id", items[0]); err != nil { + return []*schema.ResourceData{data}, err + } + if err := data.Set("job_name", items[1]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_cron_job/service_byteplus_cdn_cron_job.go b/byteplus/cdn/cdn_cron_job/service_byteplus_cdn_cron_job.go new file mode 100644 index 0000000..df1c144 --- /dev/null +++ b/byteplus/cdn/cdn_cron_job/service_byteplus_cdn_cron_job.go @@ -0,0 +1,291 @@ +package cdn_cron_job + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnCronJobService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnCronJobService(c *bp.SdkClient) *ByteplusCdnCronJobService { + return &ByteplusCdnCronJobService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnCronJobService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnCronJobService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListCronJob" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Jobs", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Jobs is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnCronJobService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + ids := strings.Split(id, ":") + if len(ids) != 2 { + return data, fmt.Errorf(" Invalid CdnCronJob Id %s ", id) + } + + req := map[string]interface{}{ + "FunctionId": ids[0], + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + var job map[string]interface{} + if job, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if job["JobName"].(string) == ids[1] { + data = job + break + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_cron_job %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnCronJobService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + ) + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("JobState", d) + if err != nil { + return nil, "", err + } + + return d, strconv.Itoa(int(status.(float64))), err + }, + } +} + +func (ByteplusCdnCronJobService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnCronJobService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateCronJob", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{}, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + functionId := d.Get("function_id").(string) + jobName := d.Get("job_name").(string) + d.SetId(fmt.Sprintf(functionId + ":" + jobName)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"0", "1", "3"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCronJobService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateCronJob", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "cron_expression": { + TargetField: "CronExpression", + ForceGet: true, + }, + "cron_type": { + TargetField: "CronType", + ForceGet: true, + }, + "parameter": { + TargetField: "Parameter", + }, + "description": { + TargetField: "Description", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + ids := strings.Split(d.Id(), ":") + if len(ids) != 2 { + return false, fmt.Errorf(" Invalid CdnCronJob Id %s ", d.Id()) + } + + (*call.SdkParam)["FunctionId"] = ids[0] + (*call.SdkParam)["jobName"] = ids[1] + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"0", "1", "3"}, + Timeout: resourceData.Timeout(schema.TimeoutUpdate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCronJobService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteCronJob", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + ids := strings.Split(d.Id(), ":") + if len(ids) != 2 { + return false, fmt.Errorf(" Invalid CdnCronJob Id %s ", d.Id()) + } + + (*call.SdkParam)["FunctionId"] = ids[0] + (*call.SdkParam)["jobName"] = ids[1] + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading corn job on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCronJobService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + NameField: "JobName", + CollectField: "cron_jobs", + } +} + +func (s *ByteplusCdnCronJobService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_cron_job_state/resource_byteplus_cdn_cron_job_state.go b/byteplus/cdn/cdn_cron_job_state/resource_byteplus_cdn_cron_job_state.go new file mode 100644 index 0000000..c43aee4 --- /dev/null +++ b/byteplus/cdn/cdn_cron_job_state/resource_byteplus_cdn_cron_job_state.go @@ -0,0 +1,118 @@ +package cdn_cron_job_state + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnCronJobState can be imported using the state:function_id:job_name, e.g. +``` +$ terraform import byteplus_cdn_cron_job_state.default state:function_id:job_name +``` + +*/ + +func ResourceByteplusCdnCronJobState() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnCronJobStateCreate, + Read: resourceByteplusCdnCronJobStateRead, + Update: resourceByteplusCdnCronJobStateUpdate, + Delete: resourceByteplusCdnCronJobStateDelete, + Importer: &schema.ResourceImporter{ + State: cronJobStateImporter, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The id of the function.", + }, + "job_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the cron job.", + }, + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"Start", "Stop"}, false), + Description: "Start or Stop of corn job, the value can be `Start` or `Stop`. \n" + + "If the target status of the action is consistent with the current status of the corn job, the action will not actually be executed.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + + // computed_fields + "job_state": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the cron job.", + }, + }, + } + return resource +} + +func resourceByteplusCdnCronJobStateCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobStateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnCronJobState()) + if err != nil { + return fmt.Errorf("error on creating cdn_cron_job_state %q, %s", d.Id(), err) + } + return resourceByteplusCdnCronJobStateRead(d, meta) +} + +func resourceByteplusCdnCronJobStateRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobStateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnCronJobState()) + if err != nil { + return fmt.Errorf("error on reading cdn_cron_job_state %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnCronJobStateUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobStateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnCronJobState()) + if err != nil { + return fmt.Errorf("error on updating cdn_cron_job_state %q, %s", d.Id(), err) + } + return resourceByteplusCdnCronJobStateRead(d, meta) +} + +func resourceByteplusCdnCronJobStateDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnCronJobStateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnCronJobState()) + if err != nil { + return fmt.Errorf("error on deleting cdn_cron_job_state %q, %s", d.Id(), err) + } + return err +} + +var cronJobStateImporter = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 3 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("function_id", items[1]); err != nil { + return []*schema.ResourceData{data}, err + } + if err := data.Set("job_name", items[2]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_cron_job_state/service_byteplus_cdn_cron_job_state.go b/byteplus/cdn/cdn_cron_job_state/service_byteplus_cdn_cron_job_state.go new file mode 100644 index 0000000..94e3475 --- /dev/null +++ b/byteplus/cdn/cdn_cron_job_state/service_byteplus_cdn_cron_job_state.go @@ -0,0 +1,303 @@ +package cdn_cron_job_state + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnCronJobStateService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnCronJobStateService(c *bp.SdkClient) *ByteplusCdnCronJobStateService { + return &ByteplusCdnCronJobStateService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnCronJobStateService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnCronJobStateService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListCronJob" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Jobs", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Jobs is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnCronJobStateService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + ids := strings.Split(id, ":") + if len(ids) != 3 { + return data, fmt.Errorf(" Invalid CdnCronJobState Id %s ", id) + } + + req := map[string]interface{}{ + "FunctionId": ids[1], + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + var job map[string]interface{} + if job, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if job["JobName"].(string) == ids[2] { + data = job + break + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_cron_job %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnCronJobStateService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + ) + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("JobState", d) + if err != nil { + return nil, "", err + } + + return d, strconv.Itoa(int(status.(float64))), err + }, + } +} + +func (ByteplusCdnCronJobStateService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnCronJobStateService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var action string + targetStatus := []string{"1"} + jobAction := resourceData.Get("action").(string) + if jobAction == "Start" { + action = "StartCronJob" + } else { + action = "StopCronJob" + targetStatus = []string{"3"} + } + + // 根据 job 当前状态判断是否执行操作 + update, err := s.describeCurrentJobStatus(resourceData, targetStatus) + if err != nil { + return []bp.Callback{{ + Err: err, + }} + } + if !update { + resourceData.SetId(fmt.Sprintf("state:%v:%v", resourceData.Get("function_id"), resourceData.Get("job_name"))) + return []bp.Callback{} + } + + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: action, + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "action": { + Ignore: true, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + functionId := resourceData.Get("function_id").(string) + jobName := resourceData.Get("job_name").(string) + d.SetId(fmt.Sprintf("state:%v:%v", functionId, jobName)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: targetStatus, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCronJobStateService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var action string + targetStatus := []string{"1"} + jobAction := resourceData.Get("action").(string) + if jobAction == "Start" { + action = "StartCronJob" + } else { + action = "StopCronJob" + targetStatus = []string{"3"} + } + + // 根据 job 当前状态判断是否执行操作 + update, err := s.describeCurrentJobStatus(resourceData, targetStatus) + if err != nil { + return []bp.Callback{{ + Err: err, + }} + } + if !update { + resourceData.SetId(fmt.Sprintf("state:%v:%v", resourceData.Get("function_id"), resourceData.Get("job_name"))) + return []bp.Callback{} + } + + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: action, + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "action": { + Ignore: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + ids := strings.Split(d.Id(), ":") + if len(ids) != 3 { + return false, fmt.Errorf(" Invalid CdnCronJobState Id %s ", d.Id()) + } + + (*call.SdkParam)["FunctionId"] = ids[1] + (*call.SdkParam)["JobName"] = ids[2] + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: targetStatus, + Timeout: resourceData.Timeout(schema.TimeoutUpdate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnCronJobStateService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnCronJobStateService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{} +} + +func (s *ByteplusCdnCronJobStateService) ReadResourceId(id string) string { + return id +} + +func (s *ByteplusCdnCronJobStateService) describeCurrentJobStatus(resourceData *schema.ResourceData, targetStatus []string) (bool, error) { + functionId := resourceData.Get("function_id").(string) + jobName := resourceData.Get("job_name").(string) + data, err := s.ReadResource(resourceData, "state:"+functionId+":"+jobName) + if err != nil { + return false, err + } + status, err := bp.ObtainSdkValue("JobState", data) + if err != nil { + return false, err + } + for _, v := range targetStatus { + // 目标状态和当前状态相同时,不执行操作 + if v == strconv.Itoa(int(status.(float64))) { + return false, nil + } + } + return true, nil +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_domain/data_source_byteplus_cdn_domains.go b/byteplus/cdn/cdn_domain/data_source_byteplus_cdn_domains.go new file mode 100644 index 0000000..ccd310a --- /dev/null +++ b/byteplus/cdn/cdn_domain/data_source_byteplus_cdn_domains.go @@ -0,0 +1,234 @@ +package cdn_domain + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnDomains() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnDomainsRead, + Schema: map[string]*schema.Schema{ + "filters": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fuzzy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Indicates the matching method. This parameter can take the following values: " + + "true: Indicates fuzzy matching. A policy is considered to meet the filtering criteria if the corresponding value of Name contains any value in the Value array. " + + "false: Indicates exact matching. A policy is considered to meet the filtering criteria if the corresponding value of Name matches any value in the Value array. " + + "Moreover, the Fuzzy value you can specify is affected by the Name value. See the description of Name. " + + "The default value of this parameter is false. " + + "Note that the matching process is case-sensitive.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates the filtering type. " + + "This parameter can take the following values: " + + "TemplateTitle: Filters domain names by the name of the bound policy. " + + "TemplateId: Filters domain names by the ID of the bound policy. " + + "For this parameter value, the Fuzzy value can only be false. " + + "TemplateType: Filters domain names by the type of the bound policy. " + + "For this parameter value, the Fuzzy value can only be false. " + + "Domain: Filters domain names by name. " + + "Status: Filters domain names by status. " + + "For this parameter value, the Fuzzy value can only be false. " + + "HTTPSSwitch: Filters domain names by the status of the HTTPS encryption service. " + + "For this parameter value, the Fuzzy value can only be false. " + + "WAFStatus: Filters domain names by the status of WAF protection. " + + "For this parameter value, the Fuzzy value can only be false. " + + "Multiple filtering conditions can be specified at the same time, but the Name in different filtering conditions cannot be the same.", + }, + "value": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Indicates the values corresponding to Name, which is an array. " + + "The values in the array are used to match against the object value. " + + "If the object value matches any value in the array, it is considered a match. " + + "Values are case-sensitive when matching. " + + "When Name is TemplateTitle or Domain, each value in Value cannot exceed 100 characters. " + + "Furthermore, " + + "When Fuzzy is false, Value can contain multiple values. " + + "When Fuzzy is true, Value can only contain one value. " + + "When Name is TemplateId, Value can only contain one value. " + + "When Name is TemplateType, Value can contain one or more of the following values: " + + "cipher: Indicates an encryption policy. " + + "service: Indicates a delivery policy. " + + "When Name is Status, Value can contain one of the following values: " + + "online: Indicates that the status of the domain name is Enabled." + + " offline: Indicates that the status of the domain name is Disabled. " + + "configuring: Indicates that the status of the domain name is Configuring. " + + "When Name is HTTPSSwitch, Value can contain one of the following values: " + + "on: Indicates that the HTTPS encryption service is enabled. " + + "off: Indicates that the HTTPS encryption service is disabled. " + + "When Name is WAFStatus, Value can contain one of the following values: " + + "on: Indicates that WAF protection is enabled. " + + "off: Indicates that WAF protection is disabled.", + }, + }, + }, + Description: "Indicates a set of filtering criteria used to obtain a list of policies that meet these criteria. " + + "If you do not specify any filtering criteria, this API returns all policies under your account. " + + "Multiple filtering criteria are related by AND, meaning only policies that meet all filtering criteria will be included in the list returned by this API. " + + "In the API response, the actual policies returned are affected by PageNum and PageSize.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "domains": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cert_info": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates information about the certificate associated with the domain name specified by Domain. " + + "If HTTPSSwitch is off, the value of this parameter is null.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cert_id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of the certificate associated with the domain name." + + " This certificate is hosted in the BytePlus Certificate Center.", + }, + }, + }, + }, + "cname": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the CNAME assigned to the domain name by CDN.", + }, + "domain": { + Type: schema.TypeString, + Computed: true, + Description: "Represents one of the domain names in the Domains list.", + }, + "https_switch": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates whether the domain name has enabled HTTPS Encryption Service. " + + "This parameter can be: " + + "on: Indicates that the service is enabled. " + + "off: Indicates that the service is disabled.", + }, + "lock_status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates whether the domain name is locked. " + + "This parameter can be: " + + "on: Indicates that the domain name is locked. " + + "In this case, you cannot use UpdateTemplateDomain to change the configurations of this domain name. " + + "off: Indicates that the domain name is not locked.", + }, + "remark": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the reason why the domain name is locked. " + + "If LockStatus is on, this parameter indicates the reason why the domain name is locked. " + + "If LockStatus is off, the value of this parameter is empty (\"\").", + }, + "service_region": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the service region enabled for the domain name. " + + "This parameter can be: " + + "outside_chinese_mainland: Indicates Global (Excluding Chinese Mainland). " + + "chinese_mainland: Indicates Chinese Mainland. " + + "global: Indicates Global.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the domain name. " + + "This parameter can be: " + + "online: Indicates the status is Enabled. " + + "offline: Indicates the status is Disabled. " + + "configuring: Indicates the status is Configuring.", + }, + "waf_status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates whether the domain name has enabled WAF Protection. " + + "This parameter can be: " + + "on: Indicates that WAF Protection is enabled. " + + "off: Indicates that WAF Protection is disabled.", + }, + "project": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the project to which the domain name belongs.", + }, + "templates": { + Type: schema.TypeList, + Computed: true, + Description: "Indicates the list of policies bound to the domain name. " + + "A domain name must and can only be bound to one delivery policy, " + + "and optionally to one encryption policy.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of a policy.", + }, + "title": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the name of the policy.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the type of the policy. This parameter can be: " + + "cipher: Indicates an encryption policy. " + + "service: Indicates a delivery policy.", + }, + "exception": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether the policy contains special configurations. " + + "Special configurations refer to those configurations that are operated by BytePlus engineers instead of users. " + + "This parameter can be: " + + "true: Indicates it contains special configurations. " + + "false: Indicates it does not contain special configurations.", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnDomainsRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnDomainService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnDomains()) +} diff --git a/byteplus/cdn/cdn_domain/resource_byteplus_cdn_domain.go b/byteplus/cdn/cdn_domain/resource_byteplus_cdn_domain.go new file mode 100644 index 0000000..f855701 --- /dev/null +++ b/byteplus/cdn/cdn_domain/resource_byteplus_cdn_domain.go @@ -0,0 +1,164 @@ +package cdn_domain + +import ( + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnDomain can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_domain.default resource_id +``` + +*/ + +func ResourceByteplusCdnDomain() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnDomainCreate, + Read: resourceByteplusCdnDomainRead, + Update: resourceByteplusCdnDomainUpdate, + Delete: resourceByteplusCdnDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Indicates a domain name you want to add. " + + "The domain name you add must meet all of the following conditions: " + + "Length does not exceed 100 characters. " + + "Cannot contain uppercase letters. " + + "Does not include any of these suffixes: zjgslb.com, yangyi19.com, volcgslb.com, veew-alb-cn1.com, bplgslb.com, bplslb.com, ttgslb.com. " + + "When you bind your domain name with a delivery policy, " + + "the origin address specified in the policy must not be the same as your domain name.", + }, + "service_template_id": { + Type: schema.TypeString, + Required: true, + Description: "Indicates a delivery policy to be bound with this domain name. " + + "You can use DescribeTemplates to obtain the ID of the delivery policy you want to bind.", + }, + "cert_id": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + httpsSwitch, ok := d.GetOk("https_switch") + if ok { + if httpsSwitch.(string) == "off" { + return true + } else if httpsSwitch.(string) == "on" { + return false + } + } + return true + }, + Description: "Indicates the ID of a certificate. " + + "This certificate is stored in the BytePlus Certificate Center and will be associated with the domain name. " + + "If HTTPSSwitch is on, this parameter is required. " + + "Before using this API, you need to grant CDN access to the Certificate Center, then upload your certificate to the BytePlus Certificate Center to obtain the ID of the certificate. " + + "It is recommended to authorize CDN access to the Certificate Center using the primary account. " + + "You can use ListCertInfo to obtain the ID of the certificate you want to associate. " + + "If HTTPSSwitch is off, this parameter does not take effect.", + }, + "cipher_template_id": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates an encryption policy to be bound with this domain name. " + + "You can use DescribeTemplates to obtain the ID of the encryption policy you want to bind. " + + "If this parameter is not specified, it means that the domain name will not be bound to any encryption policy at present.", + }, + "https_switch": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates whether to enable \"HTTPS Encryption Service\" for this domain name. " + + "This parameter can take the following values: " + + "on: Indicates to enable this service. " + + "off: Indicates not to enable this service. " + + "The default value of this parameter is off.", + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates the project to which this domain name belongs, with the default value being default.", + }, + "service_region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates the service region enabled for this domain name. " + + "This parameter can take the following values: " + + "outside_chinese_mainland: Indicates \"Global (excluding Chinese Mainland)\"." + + " chinese_mainland: Indicates \"Chinese Mainland\". " + + "global: Indicates \"Global\". " + + "The default value of this parameter is outside_chinese_mainland. " + + "Note that chinese_mainland or global are not available by default. " + + "To make the two service regions available, please submit a ticket. " + + "Also, since both regions include Chinese Mainland, " + + "you must complete the following additional actions: " + + "Perform real-name authentication for your BytePlus account. " + + "Perform ICP filing for your domain name.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the domain name. " + + "This parameter can be: " + + "online: Indicates the status is Enabled. " + + "offline: Indicates the status is Disabled. " + + "configuring: Indicates the status is Configuring.", + }, + }, + } + return resource +} + +func resourceByteplusCdnDomainCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnDomain()) + if err != nil { + return fmt.Errorf("error on creating cdn_domain %q, %s", d.Id(), err) + } + return resourceByteplusCdnDomainRead(d, meta) +} + +func resourceByteplusCdnDomainRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnDomain()) + if err != nil { + return fmt.Errorf("error on reading cdn_domain %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnDomainUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnDomain()) + if err != nil { + return fmt.Errorf("error on updating cdn_domain %q, %s", d.Id(), err) + } + return resourceByteplusCdnDomainRead(d, meta) +} + +func resourceByteplusCdnDomainDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnDomain()) + if err != nil { + return fmt.Errorf("error on deleting cdn_domain %q, %s", d.Id(), err) + } + return err +} diff --git a/byteplus/cdn/cdn_domain/service_byteplus_cdn_domain.go b/byteplus/cdn/cdn_domain/service_byteplus_cdn_domain.go new file mode 100644 index 0000000..f218a5c --- /dev/null +++ b/byteplus/cdn/cdn_domain/service_byteplus_cdn_domain.go @@ -0,0 +1,327 @@ +package cdn_domain + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnDomainService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnDomainService(c *bp.SdkClient) *ByteplusCdnDomainService { + return &ByteplusCdnDomainService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnDomainService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnDomainService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageNumberQuery(m, "PageSize", "PageNum", 100, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "DescribeTemplateDomains" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Domains", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Domains is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnDomainService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + filter := map[string]interface{}{ + "Fuzzy": false, + "Name": "Domain", + "Value": []string{id}, + } + req := map[string]interface{}{ + "Filters": []interface{}{filter}, + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_domain %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnDomainService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + failStates []string + ) + failStates = append(failStates, "Failed") + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + for _, v := range failStates { + if v == status.(string) { + return nil, "", fmt.Errorf("cdn_domain status error, status: %s", status.(string)) + } + } + return d, status.(string), err + }, + } +} + +func (s *ByteplusCdnDomainService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "AddTemplateDomain", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "https_switch": { + TargetField: "HTTPSSwitch", + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + id, _ := d.Get("domain").(string) + d.SetId(id) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"online", "offline"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (ByteplusCdnDomainService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, map[string]bp.ResponseConvert{ + "HTTPSSwitch": { + TargetField: "https_switch", + }, + "WAFStatus": { + TargetField: "waf_status", + }, + }, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnDomainService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateTemplateDomain", + ConvertMode: bp.RequestConvertInConvert, + Convert: map[string]bp.RequestConvert{ + "cert_id": { + TargetField: "CertId", + ForceGet: true, + }, + "cipher_template_id": { + TargetField: "CipherTemplateId", + ForceGet: true, + }, + "https_switch": { + TargetField: "HTTPSSwitch", + ForceGet: true, + }, + "service_region": { + TargetField: "ServiceRegion", + ForceGet: true, + }, + "service_template_id": { + TargetField: "ServiceTemplateId", + ForceGet: true, + }, + "project": { + Ignore: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if d.HasChanges("service_template_id", "service_region", + "https_switch", "cipher_template_id", "cert_id") { + (*call.SdkParam)["Domains"] = []string{d.Id()} + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"online", "offline"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnDomainService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + status := resourceData.Get("status").(string) + if status == "online" { + stopCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "StopCdnDomain", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "Domain": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + Refresh: &bp.StateRefresh{ + Target: []string{"offline"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, stopCallback) + } + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteCdnDomain", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "Domain": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + }, + } + callbacks = append(callbacks, callback) + return callbacks + +} + +func (s *ByteplusCdnDomainService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + RequestConverts: map[string]bp.RequestConvert{ + "filters": { + TargetField: "Filters", + ConvertType: bp.ConvertJsonObjectArray, + NextLevelConvert: map[string]bp.RequestConvert{ + "value": { + TargetField: "Value", + ConvertType: bp.ConvertJsonArray, + }, + }, + }, + }, + NameField: "Domain", + IdField: "Domain", + CollectField: "domains", + ResponseConverts: map[string]bp.ResponseConvert{ + "HTTPSSwitch": { + TargetField: "https_switch", + }, + "WAFStatus": { + TargetField: "waf_status", + }, + }, + } +} + +func (s *ByteplusCdnDomainService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} + +func (s *ByteplusCdnDomainService) ProjectTrn() *bp.ProjectTrn { + return &bp.ProjectTrn{ + ServiceName: "CDN", + ResourceType: "Domain", + ProjectResponseField: "Project", + ProjectSchemaField: "project", + } +} diff --git a/byteplus/cdn/cdn_domain_enabler/resource_byteplus_cdn_domain_enabler.go b/byteplus/cdn/cdn_domain_enabler/resource_byteplus_cdn_domain_enabler.go new file mode 100644 index 0000000..ded789d --- /dev/null +++ b/byteplus/cdn/cdn_domain_enabler/resource_byteplus_cdn_domain_enabler.go @@ -0,0 +1,88 @@ +package cdn_domain_enabler + +import ( + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnDomainEnabler can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_domain_enabler.default enabler:resource_id +``` + +*/ + +func ResourceByteplusCdnDomainEnabler() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnDomainEnablerCreate, + Read: resourceByteplusCdnDomainEnablerRead, + Delete: resourceByteplusCdnDomainEnablerDelete, + Importer: &schema.ResourceImporter{ + State: cdnDomainEnablerState, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Indicate the domain name you want to enable.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of the domain.", + }, + }, + } + return resource +} + +func resourceByteplusCdnDomainEnablerCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainEnablerService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnDomainEnabler()) + if err != nil { + return fmt.Errorf("error on creating cdn_domain_enabler %q, %s", d.Id(), err) + } + return resourceByteplusCdnDomainEnablerRead(d, meta) +} + +func resourceByteplusCdnDomainEnablerRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainEnablerService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnDomainEnabler()) + if err != nil { + return fmt.Errorf("error on reading cdn_domain_enabler %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnDomainEnablerDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnDomainEnablerService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnDomainEnabler()) + if err != nil { + return fmt.Errorf("error on deleting cdn_domain_enabler %q, %s", d.Id(), err) + } + return err +} + +var cdnDomainEnablerState = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 2 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("domain", items[1]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_domain_enabler/service_byteplus_cdn_domain_enabler.go b/byteplus/cdn/cdn_domain_enabler/service_byteplus_cdn_domain_enabler.go new file mode 100644 index 0000000..d9aaff5 --- /dev/null +++ b/byteplus/cdn/cdn_domain_enabler/service_byteplus_cdn_domain_enabler.go @@ -0,0 +1,268 @@ +package cdn_domain_enabler + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnDomainEnablerService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnDomainEnablerService(c *bp.SdkClient) *ByteplusCdnDomainEnablerService { + return &ByteplusCdnDomainEnablerService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnDomainEnablerService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnDomainEnablerService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageNumberQuery(m, "PageSize", "PageNum", 100, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListCdnDomains" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Data", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Domains is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnDomainEnablerService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + ids := strings.Split(id, ":") + if len(ids) != 2 { + return nil, fmt.Errorf("err cdn domain enabler id") + } + + req := map[string]interface{}{ + "Domain": ids[1], + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_domain_enabler %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnDomainEnablerService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + failStates []string + ) + failStates = append(failStates, "Failed") + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + for _, v := range failStates { + if v == status.(string) { + return nil, "", fmt.Errorf("cdn_domain_enabler status error, status: %s", status.(string)) + } + } + return d, status.(string), err + }, + } +} + +func (s *ByteplusCdnDomainEnablerService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "StartCdnDomain", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{}, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + domain := d.Get("domain").(string) + req := map[string]interface{}{ + "Domain": domain, + } + logger.Debug(logger.ReqFormat, "ListCdnDomains", req) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo("ListCdnDomains"), &req) + if err != nil { + return nil, err + } + logger.Debug(logger.RespFormat, "ListCdnDomains", req, *resp) + data, err := bp.ObtainSdkValue("Result.Data", *resp) + if err != nil { + return nil, err + } + domains := data.([]interface{}) + var status string + if len(domains) > 0 { + do := domains[0].(map[string]interface{}) + status = do["Status"].(string) + } else { + return nil, fmt.Errorf("Domain not found ") + } + // 已经online就什么也不做,直接跳过 + if status == "online" { + return nil, nil + } else { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + } + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + domain := d.Get("domain").(string) + d.SetId(fmt.Sprintf("enabler:%s", domain)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"online"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (ByteplusCdnDomainEnablerService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnDomainEnablerService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnDomainEnablerService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "StopCdnDomain", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + ids := strings.Split(d.Id(), ":") + (*call.SdkParam)["Domain"] = ids[1] + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + domain := d.Get("domain").(string) + req := map[string]interface{}{ + "Domain": domain, + } + logger.Debug(logger.ReqFormat, "ListCdnDomains", req) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo("ListCdnDomains"), &req) + if err != nil { + return nil, err + } + logger.Debug(logger.RespFormat, "ListCdnDomains", req, *resp) + data, err := bp.ObtainSdkValue("Result.Data", *resp) + if err != nil { + return nil, err + } + domains := data.([]interface{}) + var status string + if len(domains) > 0 { + do := domains[0].(map[string]interface{}) + status = do["Status"].(string) + } else { + return nil, fmt.Errorf("Domain not found ") + } + if status == "offline" { + return nil, nil + } else { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + } + }, + Refresh: &bp.StateRefresh{ + Target: []string{"offline"}, + Timeout: resourceData.Timeout(schema.TimeoutDelete), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnDomainEnablerService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{} +} + +func (s *ByteplusCdnDomainEnablerService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_edge_function/data_source_byteplus_cdn_edge_functions.go b/byteplus/cdn/cdn_edge_function/data_source_byteplus_cdn_edge_functions.go new file mode 100644 index 0000000..f009e9c --- /dev/null +++ b/byteplus/cdn/cdn_edge_function/data_source_byteplus_cdn_edge_functions.go @@ -0,0 +1,167 @@ +package cdn_edge_function + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnEdgeFunctions() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnEdgeFunctionsRead, + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Optional: true, + Description: "The status of the function. \n100: running. \n400: unassociated. \n500: configuring.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "edge_functions": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the edge function.", + }, + "function_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the edge function.", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the edge function.", + }, + "remark": { + Type: schema.TypeString, + Computed: true, + Description: "The remark of the edge function.", + }, + "status": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the edge function.", + }, + "project_name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the project to which the edge function belongs.", + }, + "account_identity": { + Type: schema.TypeInt, + Computed: true, + Description: "The account id of the edge function.", + }, + "creator": { + Type: schema.TypeString, + Computed: true, + Description: "The creator of the edge function.", + }, + "user_identity": { + Type: schema.TypeInt, + Computed: true, + Description: "The user id of the edge function.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The create time of the edge function. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the edge function. Displayed in UNIX timestamp format.", + }, + "source_code": { + Type: schema.TypeString, + Computed: true, + Description: "The latest code content of the edge function. The code is transformed into a Base64-encoded format.", + }, + "domain": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "The domain name bound to the edge function.", + }, + "envs": { + Type: schema.TypeList, + Computed: true, + Description: "The environment variables of the edge function.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + Description: "The key of the environment variable.", + }, + "value": { + Type: schema.TypeString, + Computed: true, + Description: "The value of the environment variable.", + }, + }, + }, + }, + "continent_cluster": { + Type: schema.TypeList, + Computed: true, + Description: "The canary cluster info of the edge function.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "country": { + Type: schema.TypeString, + Computed: true, + Description: "The country where the cluster is located.", + }, + "continent": { + Type: schema.TypeString, + Computed: true, + Description: "The continent where the cluster is located.", + }, + "cluster_type": { + Type: schema.TypeInt, + Computed: true, + Description: "The type of the cluster.", + }, + "traffics": { + Type: schema.TypeString, + Computed: true, + Description: "The versions of the function deployed on this cluster.", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnEdgeFunctionsRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnEdgeFunctionService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnEdgeFunctions()) +} diff --git a/byteplus/cdn/cdn_edge_function/resource_byteplus_cdn_edge_function.go b/byteplus/cdn/cdn_edge_function/resource_byteplus_cdn_edge_function.go new file mode 100644 index 0000000..4576bbd --- /dev/null +++ b/byteplus/cdn/cdn_edge_function/resource_byteplus_cdn_edge_function.go @@ -0,0 +1,180 @@ +package cdn_edge_function + +import ( + "bytes" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnEdgeFunction can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_edge_function.default resource_id +``` + +*/ + +func ResourceByteplusCdnEdgeFunction() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnEdgeFunctionCreate, + Read: resourceByteplusCdnEdgeFunctionRead, + Update: resourceByteplusCdnEdgeFunctionUpdate, + Delete: resourceByteplusCdnEdgeFunctionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the edge function.", + }, + "remark": { + Type: schema.TypeString, + Optional: true, + Description: "The remark of the edge function.", + }, + "project_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The name of the project to which the edge function belongs, defaulting to `default`.", + }, + "source_code": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Code content. The input requirements are as follows: \nNot empty.\nValue after base64 encoding.", + }, + "envs": { + Type: schema.TypeSet, + Optional: true, + Set: envsHash, + Description: "The environment variables of the edge function.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + Description: "The key of the environment variable.", + }, + "value": { + Type: schema.TypeString, + Required: true, + Description: "The value of the environment variable.", + }, + }, + }, + }, + "canary_countries": { + Type: schema.TypeSet, + Optional: true, + Set: schema.HashString, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "The array of countries where the canary cluster is located.", + }, + + // computed fields + "status": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the edge function.", + }, + "account_identity": { + Type: schema.TypeInt, + Computed: true, + Description: "The account id of the edge function.", + }, + "creator": { + Type: schema.TypeString, + Computed: true, + Description: "The creator of the edge function.", + }, + "user_identity": { + Type: schema.TypeInt, + Computed: true, + Description: "The user id of the edge function.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The create time of the edge function. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the edge function. Displayed in UNIX timestamp format.", + }, + "domain": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "The domain name bound to the edge function.", + }, + }, + } + return resource +} + +func resourceByteplusCdnEdgeFunctionCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnEdgeFunction()) + if err != nil { + return fmt.Errorf("error on creating cdn_edge_function %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnEdgeFunction()) + if err != nil { + return fmt.Errorf("error on reading cdn_edge_function %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnEdgeFunctionUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnEdgeFunction()) + if err != nil { + return fmt.Errorf("error on updating cdn_edge_function %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnEdgeFunction()) + if err != nil { + return fmt.Errorf("error on deleting cdn_edge_function %q, %s", d.Id(), err) + } + return err +} + +var envsHash = func(v interface{}) int { + if v == nil { + return hashcode.String("") + } + m := v.(map[string]interface{}) + var ( + buf bytes.Buffer + ) + buf.WriteString(fmt.Sprintf("%v#%v", m["key"], m["value"])) + return hashcode.String(buf.String()) +} diff --git a/byteplus/cdn/cdn_edge_function/service_byteplus_cdn_edge_function.go b/byteplus/cdn/cdn_edge_function/service_byteplus_cdn_edge_function.go new file mode 100644 index 0000000..491b81b --- /dev/null +++ b/byteplus/cdn/cdn_edge_function/service_byteplus_cdn_edge_function.go @@ -0,0 +1,679 @@ +package cdn_edge_function + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnEdgeFunctionService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnEdgeFunctionService(c *bp.SdkClient) *ByteplusCdnEdgeFunctionService { + return &ByteplusCdnEdgeFunctionService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnEdgeFunctionService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnEdgeFunctionService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + data, err = bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListSparrow" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Sparrows", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Sparrows is not Slice") + } + return data, err + }) + + for _, v := range data { + function, ok := v.(map[string]interface{}) + if !ok { + return data, fmt.Errorf(" The Sparrow of Result is not map ") + } + functionId := function["FunctionId"] + + // 查询函数最新版本代码 + codeAction := "GetSourceCode" + codeReq := map[string]interface{}{ + "FunctionId": functionId, + } + logger.Debug(logger.ReqFormat, codeAction, codeReq) + codeResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(codeAction), &codeReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, codeAction, codeResp) + sourceCode, err := bp.ObtainSdkValue("Result.SourceCode", *codeResp) + if err != nil { + return data, err + } + function["SourceCode"] = sourceCode + + // 查询函数环境变量 + envAction := "GetEnv" + envReq := map[string]interface{}{ + "FunctionId": functionId, + } + logger.Debug(logger.ReqFormat, envAction, envReq) + envResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(envAction), &envReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, envAction, envResp) + envs, err := bp.ObtainSdkValue("Result.Envs", *envResp) + if err != nil { + return data, err + } + function["Envs"] = envs + + // 查询函数灰度发布集群 + canaryAction := "ListContinentCluster" + canaryReq := map[string]interface{}{ + "FunctionId": functionId, + "ClusterType": 100, + } + logger.Debug(logger.ReqFormat, canaryAction, canaryReq) + canaryResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(canaryAction), &canaryReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, canaryAction, canaryResp) + clusters, err := bp.ObtainSdkValue("Result.ContinentCluster", *canaryResp) + if err != nil { + return data, err + } + function["ContinentCluster"] = clusters + } + + return data, err +} + +func (s *ByteplusCdnEdgeFunctionService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + result interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + action := "GetSparrow" + req := map[string]interface{}{ + "FunctionId": id, + } + logger.Debug(logger.ReqFormat, action, req) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(action), &req) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, action, resp) + result, err = bp.ObtainSdkValue("Result.Sparrow", *resp) + if err != nil { + return data, err + } + if data, ok = result.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if len(data) == 0 { + return data, fmt.Errorf("edge_function %s is not exist ", id) + } + + // 查询函数最新版本代码 + codeAction := "GetSourceCode" + codeReq := map[string]interface{}{ + "FunctionId": id, + } + logger.Debug(logger.ReqFormat, codeAction, codeReq) + codeResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(codeAction), &codeReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, codeAction, codeResp) + sourceCode, err := bp.ObtainSdkValue("Result.SourceCode", *codeResp) + if err != nil { + return data, err + } + data["SourceCode"] = sourceCode + + // 查询函数环境变量 + envAction := "GetEnv" + envReq := map[string]interface{}{ + "FunctionId": id, + } + logger.Debug(logger.ReqFormat, envAction, envReq) + envResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(envAction), &envReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, envAction, envResp) + envs, err := bp.ObtainSdkValue("Result.Envs", *envResp) + if err != nil { + return data, err + } + data["Envs"] = envs + + // 查询函数灰度发布集群 + canaryAction := "ListContinentCluster" + canaryReq := map[string]interface{}{ + "FunctionId": id, + "ClusterType": 100, + } + logger.Debug(logger.ReqFormat, canaryAction, canaryReq) + canaryResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(canaryAction), &canaryReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, canaryAction, canaryResp) + clusters, err := bp.ObtainSdkValue("Result.ContinentCluster", *canaryResp) + if err != nil { + return data, err + } + clusterArr, ok := clusters.([]interface{}) + if !ok { + return data, fmt.Errorf("Result.ContinentCluster is not slice") + } + var countries []interface{} + for _, v := range clusterArr { + cluster, ok := v.(map[string]interface{}) + if !ok { + return data, fmt.Errorf("Result.ContinentCluster value is not map") + } + countries = append(countries, cluster["Country"]) + } + data["CanaryCountries"] = countries + + return data, err +} + +func (s *ByteplusCdnEdgeFunctionService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + ) + + // 添加重试操作 + if err = resource.Retry(5*time.Minute, func() *resource.RetryError { + d, err = s.ReadResource(resourceData, id) + if err != nil { + if bp.ResourceNotFoundError(err) || bp.AccessDeniedError(err) { + return resource.RetryableError(err) + } else { + return resource.NonRetryableError(err) + } + } + return nil + }); err != nil { + return nil, "", err + } + + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + + return d, strconv.Itoa(int(status.(float64))), err + }, + } +} + +func (ByteplusCdnEdgeFunctionService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnEdgeFunctionService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateSparrow", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "name": { + TargetField: "Name", + }, + "remark": { + TargetField: "Remark", + }, + "project_name": { + TargetField: "ProjectName", + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + id, _ := bp.ObtainSdkValue("Result.FunctionId", *resp) + d.SetId(id.(string)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, callback) + + if _, exist := resourceData.GetOk("source_code"); exist { + codeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateSourceCode", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "source_code": { + TargetField: "SourceCode", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["FunctionId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, codeCallback) + } + + if _, exist := resourceData.GetOk("envs"); exist { + codeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "AddEnv", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "envs": { + TargetField: "Envs", + ConvertType: bp.ConvertJsonObjectArray, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["FunctionId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, codeCallback) + } + + if _, exist := resourceData.GetOk("canary_countries"); exist { + codeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateCountryCluster", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "canary_countries": { + TargetField: "Countries", + ConvertType: bp.ConvertJsonArray, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["FunctionId"] = d.Id() + (*call.SdkParam)["ClusterType"] = 100 + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, codeCallback) + } + + return callbacks +} + +func (s *ByteplusCdnEdgeFunctionService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + + if resourceData.HasChanges("name", "remark") { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateSparrow", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "name": { + TargetField: "Name", + ForceGet: true, + }, + "remark": { + TargetField: "Remark", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["FunctionId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutUpdate), + }, + }, + } + callbacks = append(callbacks, callback) + } + + if resourceData.HasChange("source_code") { + codeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateSourceCode", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "source_code": { + TargetField: "SourceCode", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["FunctionId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"100", "400", "500"}, + Timeout: resourceData.Timeout(schema.TimeoutUpdate), + }, + }, + } + callbacks = append(callbacks, codeCallback) + } + + if resourceData.HasChange("envs") { + callbacks = s.updateEnvs(resourceData, callbacks) + } + + if resourceData.HasChange("canary_countries") { + callbacks = s.updateCanaryCountries(resourceData, callbacks) + } + + return callbacks +} + +func (s *ByteplusCdnEdgeFunctionService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteSparrow", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "FunctionId": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading edge function on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnEdgeFunctionService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + NameField: "Name", + IdField: "FunctionId", + CollectField: "edge_functions", + ResponseConverts: map[string]bp.ResponseConvert{ + "FunctionId": { + TargetField: "id", + KeepDefault: true, + }, + }, + } +} + +func (s *ByteplusCdnEdgeFunctionService) ReadResourceId(id string) string { + return id +} + +func (s *ByteplusCdnEdgeFunctionService) updateEnvs(resourceData *schema.ResourceData, callbacks []bp.Callback) []bp.Callback { + addedEnvs, removedEnvs, _, _ := bp.GetSetDifference("envs", resourceData, envsHash, false) + + removeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteEnv", + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if removedEnvs != nil && len(removedEnvs.List()) > 0 { + (*call.SdkParam)["FunctionId"] = resourceData.Id() + (*call.SdkParam)["EnvKeys"] = make([]string, 0) + for _, v := range removedEnvs.List() { + env, ok := v.(map[string]interface{}) + if !ok { + return false, fmt.Errorf(" The env is not map ") + } + (*call.SdkParam)["EnvKeys"] = append((*call.SdkParam)["EnvKeys"].([]string), env["key"].(string)) + } + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + }, + } + callbacks = append(callbacks, removeCallback) + + addCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "AddEnv", + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if addedEnvs != nil && len(addedEnvs.List()) > 0 { + (*call.SdkParam)["FunctionId"] = resourceData.Id() + (*call.SdkParam)["Envs"] = make([]map[string]interface{}, 0) + for _, v := range addedEnvs.List() { + env, ok := v.(map[string]interface{}) + if !ok { + return false, fmt.Errorf(" The env is not map ") + } + (*call.SdkParam)["Envs"] = append((*call.SdkParam)["Envs"].([]map[string]interface{}), env) + } + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + }, + } + callbacks = append(callbacks, addCallback) + + return callbacks +} + +func (s *ByteplusCdnEdgeFunctionService) updateCanaryCountries(resourceData *schema.ResourceData, callbacks []bp.Callback) []bp.Callback { + addedCountries, removedCountries, _, _ := bp.GetSetDifference("canary_countries", resourceData, schema.HashString, false) + + removeCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateCountryCluster", + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if removedCountries != nil && len(removedCountries.List()) > 0 { + (*call.SdkParam)["FunctionId"] = resourceData.Id() + (*call.SdkParam)["ClusterType"] = 200 + (*call.SdkParam)["Countries"] = removedCountries.List() + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + }, + } + callbacks = append(callbacks, removeCallback) + + addCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateCountryCluster", + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if addedCountries != nil && len(addedCountries.List()) > 0 { + (*call.SdkParam)["FunctionId"] = resourceData.Id() + (*call.SdkParam)["ClusterType"] = 100 + (*call.SdkParam)["Countries"] = addedCountries.List() + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + }, + } + callbacks = append(callbacks, addCallback) + + return callbacks +} + +func (s *ByteplusCdnEdgeFunctionService) ProjectTrn() *bp.ProjectTrn { + return &bp.ProjectTrn{ + ServiceName: "CDN", + ResourceType: "function", + ProjectResponseField: "ProjectName", + ProjectSchemaField: "project_name", + } +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_edge_function_associate/resource_byteplus_cdn_edge_function_associate.go b/byteplus/cdn/cdn_edge_function_associate/resource_byteplus_cdn_edge_function_associate.go new file mode 100644 index 0000000..fbcb4fd --- /dev/null +++ b/byteplus/cdn/cdn_edge_function_associate/resource_byteplus_cdn_edge_function_associate.go @@ -0,0 +1,100 @@ +package cdn_edge_function_associate + +import ( + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnEdgeFunctionAssociate can be imported using the function_id:domain, e.g. +``` +$ terraform import byteplus_cdn_edge_function_associate.default function_id:domain +``` + +*/ + +func ResourceByteplusCdnEdgeFunctionAssociate() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnEdgeFunctionAssociateCreate, + Read: resourceByteplusCdnEdgeFunctionAssociateRead, + Delete: resourceByteplusCdnEdgeFunctionAssociateDelete, + Importer: &schema.ResourceImporter{ + State: functionAssociateImporter, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The id of the function for which you want to bind to domain.", + }, + "domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The domain name which you wish to bind with the function.", + }, + }, + } + return resource +} + +func resourceByteplusCdnEdgeFunctionAssociateCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionAssociateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnEdgeFunctionAssociate()) + if err != nil { + return fmt.Errorf("error on creating cdn_edge_function_associate %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionAssociateRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionAssociateRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionAssociateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnEdgeFunctionAssociate()) + if err != nil { + return fmt.Errorf("error on reading cdn_edge_function_associate %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnEdgeFunctionAssociateUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionAssociateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnEdgeFunctionAssociate()) + if err != nil { + return fmt.Errorf("error on updating cdn_edge_function_associate %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionAssociateRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionAssociateDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionAssociateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnEdgeFunctionAssociate()) + if err != nil { + return fmt.Errorf("error on deleting cdn_edge_function_associate %q, %s", d.Id(), err) + } + return err +} + +var functionAssociateImporter = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 2 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("function_id", items[0]); err != nil { + return []*schema.ResourceData{data}, err + } + if err := data.Set("domain", items[1]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_edge_function_associate/service_byteplus_cdn_edge_function_associate.go b/byteplus/cdn/cdn_edge_function_associate/service_byteplus_cdn_edge_function_associate.go new file mode 100644 index 0000000..0391ef1 --- /dev/null +++ b/byteplus/cdn/cdn_edge_function_associate/service_byteplus_cdn_edge_function_associate.go @@ -0,0 +1,235 @@ +package cdn_edge_function_associate + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnEdgeFunctionAssociateService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnEdgeFunctionAssociateService(c *bp.SdkClient) *ByteplusCdnEdgeFunctionAssociateService { + return &ByteplusCdnEdgeFunctionAssociateService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListSparrowDomains" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Domains", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Domains is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + ids := strings.Split(id, ":") + if len(ids) != 2 { + return data, fmt.Errorf(" Invalid CdnFunctionAssociate Id %s ", id) + } + + req := map[string]interface{}{ + "FunctionId": ids[0], + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + var result map[string]interface{} + if result, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if result["Domain"].(string) == ids[1] { + data = result + break + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_edge_function_associate %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + ) + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("BindStatus", d) + if err != nil { + return nil, "", err + } + + return d, strconv.Itoa(int(status.(float64))), err + }, + } +} + +func (ByteplusCdnEdgeFunctionAssociateService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "SparrowBindDomains", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "domain": { + Ignore: true, + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + domain := d.Get("domain").(string) + (*call.SdkParam)["Domains"] = []string{domain} + + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + functionId := d.Get("function_id").(string) + domain := d.Get("domain").(string) + d.SetId(fmt.Sprintf(functionId + ":" + domain)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"1"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "SparrowUnBindDomains", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "Id": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + ids := strings.Split(d.Id(), ":") + if len(ids) != 2 { + return nil, fmt.Errorf(" Invalid CdnFunctionAssociate Id %s ", d.Id()) + } + (*call.SdkParam)["FunctionId"] = ids[0] + (*call.SdkParam)["Domains"] = []string{ids[1]} + + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + Refresh: &bp.StateRefresh{ + Target: []string{"2"}, + Timeout: resourceData.Timeout(schema.TimeoutDelete), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{} +} + +func (s *ByteplusCdnEdgeFunctionAssociateService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_edge_function_publish/data_source_byteplus_cdn_edge_function_publishes.go b/byteplus/cdn/cdn_edge_function_publish/data_source_byteplus_cdn_edge_function_publishes.go new file mode 100644 index 0000000..877d8ba --- /dev/null +++ b/byteplus/cdn/cdn_edge_function_publish/data_source_byteplus_cdn_edge_function_publishes.go @@ -0,0 +1,79 @@ +package cdn_edge_function_publish + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func DataSourceByteplusCdnEdgeFunctionPublishs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnEdgeFunctionPublishsRead, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + Description: "The id of the function.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "tickets": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ticket_id": { + Type: schema.TypeInt, + Computed: true, + Description: "The release record id.", + }, + "function_id": { + Type: schema.TypeString, + Computed: true, + Description: "The function id.", + }, + "content": { + Type: schema.TypeString, + Computed: true, + Description: "The content of the release record.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the release record.", + }, + "creator": { + Type: schema.TypeString, + Computed: true, + Description: "The creator of the release record.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The create time of the release record. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the release record. Displayed in UNIX timestamp format.", + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnEdgeFunctionPublishsRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnEdgeFunctionPublishService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnEdgeFunctionPublishs()) +} diff --git a/byteplus/cdn/cdn_edge_function_publish/resource_byteplus_cdn_edge_function_publish.go b/byteplus/cdn/cdn_edge_function_publish/resource_byteplus_cdn_edge_function_publish.go new file mode 100644 index 0000000..b519019 --- /dev/null +++ b/byteplus/cdn/cdn_edge_function_publish/resource_byteplus_cdn_edge_function_publish.go @@ -0,0 +1,153 @@ +package cdn_edge_function_publish + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "log" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnEdgeFunctionPublish can be imported using the function_id:ticket_id, e.g. +``` +$ terraform import byteplus_cdn_edge_function_publish.default function_id:ticket_id +``` + +*/ + +func ResourceByteplusCdnEdgeFunctionPublish() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnEdgeFunctionPublishCreate, + Read: resourceByteplusCdnEdgeFunctionPublishRead, + Delete: resourceByteplusCdnEdgeFunctionPublishDelete, + Importer: &schema.ResourceImporter{ + State: functionPublishImporter, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The ID of the function to which you want publish.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The description for this release.", + }, + "publish_action": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"FullPublish", "CanaryPublish", "SnapshotPublish"}, false), + Description: "The publish action of the edge function. Valid values: `FullPublish`, `CanaryPublish`, `SnapshotPublish`.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + "publish_type": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntInSlice([]int{100, 200}), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + action := d.Get("publish_action") + return action.(string) != "SnapshotPublish" + }, + Description: "The publish type of SnapshotPublish: \n200: FullPublish\n100: CanaryPublish. This field is required and valid when the `publish_action` is `SnapshotPublish`.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + "version_tag": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + action := d.Get("publish_action") + return action.(string) != "SnapshotPublish" + }, + Description: "The specified version number to be published. This field is required and valid when the `publish_action` is `SnapshotPublish`.\n " + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + + // computed fields + "content": { + Type: schema.TypeString, + Computed: true, + Description: "The content of the release record.", + }, + "creator": { + Type: schema.TypeString, + Computed: true, + Description: "The creator of the release record.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The create time of the release record. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the release record. Displayed in UNIX timestamp format.", + }, + }, + } + return resource +} + +func resourceByteplusCdnEdgeFunctionPublishCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionPublishService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnEdgeFunctionPublish()) + if err != nil { + return fmt.Errorf("error on creating cdn_edge_function_publish %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionPublishRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionPublishRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionPublishService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnEdgeFunctionPublish()) + if err != nil { + return fmt.Errorf("error on reading cdn_edge_function_publish %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnEdgeFunctionPublishUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnEdgeFunctionPublishService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnEdgeFunctionPublish()) + if err != nil { + return fmt.Errorf("error on updating cdn_edge_function_publish %q, %s", d.Id(), err) + } + return resourceByteplusCdnEdgeFunctionPublishRead(d, meta) +} + +func resourceByteplusCdnEdgeFunctionPublishDelete(d *schema.ResourceData, meta interface{}) (err error) { + log.Printf("[DEBUG] deleting a byteplus_cdn_edge_function_publish resource will only remove the publish record from terraform state.") + service := NewCdnEdgeFunctionPublishService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnEdgeFunctionPublish()) + if err != nil { + return fmt.Errorf("error on deleting cdn_edge_function_publish %q, %s", d.Id(), err) + } + return err +} + +var functionPublishImporter = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 2 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("function_id", items[0]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_edge_function_publish/service_byteplus_cdn_edge_function_publish.go b/byteplus/cdn/cdn_edge_function_publish/service_byteplus_cdn_edge_function_publish.go new file mode 100644 index 0000000..2bbbbe7 --- /dev/null +++ b/byteplus/cdn/cdn_edge_function_publish/service_byteplus_cdn_edge_function_publish.go @@ -0,0 +1,237 @@ +package cdn_edge_function_publish + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnEdgeFunctionPublishService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnEdgeFunctionPublishService(c *bp.SdkClient) *ByteplusCdnEdgeFunctionPublishService { + return &ByteplusCdnEdgeFunctionPublishService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnEdgeFunctionPublishService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnEdgeFunctionPublishService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + m["OrderType"] = "create_time" + return bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListTicket" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Tickets", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Tickets is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnEdgeFunctionPublishService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + result interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + ids := strings.Split(id, ":") + if len(ids) != 2 { + return data, fmt.Errorf(" Invalid CdnFunctionPublish Id %s ", id) + } + ticketId, err := strconv.Atoi(ids[1]) + if err != nil { + return data, fmt.Errorf(" TicketId cannot convert to int: %v ", ids[1]) + } + + action := "GetTicket" + req := map[string]interface{}{ + "FunctionId": ids[0], + "TicketId": ticketId, + } + logger.Debug(logger.ReqFormat, action, req) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(action), &req) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, action, resp) + result, err = bp.ObtainSdkValue("Result.Ticket", *resp) + if err != nil { + return data, err + } + if data, ok = result.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if len(data) == 0 { + return data, fmt.Errorf("edge_function_publish %s is not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnEdgeFunctionPublishService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + ) + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + + return d, strconv.Itoa(int(status.(float64))), err + }, + } +} + +func (ByteplusCdnEdgeFunctionPublishService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnEdgeFunctionPublishService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var ( + action string + targetStatus []string + ) + publishAction := resourceData.Get("publish_action").(string) + if publishAction == "FullPublish" { + action = "FullPublish" + targetStatus = []string{"200"} + } else if publishAction == "CanaryPublish" { + action = "CanaryPublish" + targetStatus = []string{"100"} + } else { + action = "SnapshotPublish" + publishType := resourceData.Get("publish_type") + if publishType == 100 { + targetStatus = []string{"100"} + } else if publishType == 200 { + targetStatus = []string{"200"} + } + } + + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: action, + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{}, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + ticketId, _ := bp.ObtainSdkValue("Result.TicketId", *resp) + functionId := d.Get("function_id") + d.SetId(functionId.(string) + ":" + strconv.Itoa(int(ticketId.(float64)))) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: targetStatus, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnEdgeFunctionPublishService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnEdgeFunctionPublishService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + return []bp.Callback{} +} + +func (s *ByteplusCdnEdgeFunctionPublishService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + CollectField: "tickets", + ResponseConverts: map[string]bp.ResponseConvert{ + "Id": { + TargetField: "ticket_id", + }, + }, + } +} + +func (s *ByteplusCdnEdgeFunctionPublishService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_kv/data_source_byteplus_cdn_kvs.go b/byteplus/cdn/cdn_kv/data_source_byteplus_cdn_kvs.go new file mode 100644 index 0000000..24029ef --- /dev/null +++ b/byteplus/cdn/cdn_kv/data_source_byteplus_cdn_kvs.go @@ -0,0 +1,96 @@ +package cdn_kv + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnKvs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnKvsRead, + Schema: map[string]*schema.Schema{ + "namespace_id": { + Type: schema.TypeString, + Required: true, + Description: "The id of the kv namespace.", + }, + "namespace": { + Type: schema.TypeString, + Required: true, + Description: "The name of the kv namespace.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "namespace_keys": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespace_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the kv namespace key.", + }, + "namespace": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the kv namespace key.", + }, + "key": { + Type: schema.TypeString, + Computed: true, + Description: "The key of the kv namespace key.", + }, + "key_status": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the kv namespace key.", + }, + "ddl": { + Type: schema.TypeInt, + Computed: true, + Description: "Data expiration time. After the data expires, the Value in the Key will be inaccessible.\nDisplayed in UNIX timestamp format.\n0: Permanent storage.", + }, + "value": { + Type: schema.TypeString, + Computed: true, + Description: "The value of the kv namespace key.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The creation time of the kv namespace key. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the kv namespace key. Displayed in UNIX timestamp format.", + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnKvsRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnKvService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnKvs()) +} diff --git a/byteplus/cdn/cdn_kv/resource_byteplus_cdn_kv.go b/byteplus/cdn/cdn_kv/resource_byteplus_cdn_kv.go new file mode 100644 index 0000000..533aa99 --- /dev/null +++ b/byteplus/cdn/cdn_kv/resource_byteplus_cdn_kv.go @@ -0,0 +1,144 @@ +package cdn_kv + +import ( + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnKv can be imported using the namespace_id:namespace:key, e.g. +``` +$ terraform import byteplus_cdn_kv.default namespace_id:namespace:key +``` + +*/ + +func ResourceByteplusCdnKv() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnKvCreate, + Read: resourceByteplusCdnKvRead, + Update: resourceByteplusCdnKvUpdate, + Delete: resourceByteplusCdnKvDelete, + Importer: &schema.ResourceImporter{ + State: cdnKvImporter, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "namespace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The id of the kv namespace.", + }, + "namespace": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the kv namespace.", + }, + "key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The key of the kv namespace.", + }, + "value": { + Type: schema.TypeString, + Required: true, + Description: "The value of the kv namespace key. Single Value upload data does not exceed 128KB. This field must be encrypted with base64.", + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + Description: "Set the data storage time. Unit: second. After the data expires, the Value in the Key will be inaccessible.\nIf this parameter is not specified or the parameter value is 0, it is stored permanently by default.\nThe storage time cannot be less than 60s.\n" + + "When importing resources, this attribute will not be imported. If this attribute is set, please use lifecycle and ignore_changes ignore changes in fields.", + }, + + // computed fields + "key_status": { + Type: schema.TypeInt, + Computed: true, + Description: "The status of the kv namespace key.", + }, + "ddl": { + Type: schema.TypeInt, + Computed: true, + Description: "Data expiration time. After the data expires, the Value in the Key will be inaccessible.\nDisplayed in UNIX timestamp format.\n0: Permanent storage.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The creation time of the kv namespace key. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the kv namespace key. Displayed in UNIX timestamp format.", + }, + }, + } + return resource +} + +func resourceByteplusCdnKvCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnKv()) + if err != nil { + return fmt.Errorf("error on creating cdn_kv %q, %s", d.Id(), err) + } + return resourceByteplusCdnKvRead(d, meta) +} + +func resourceByteplusCdnKvRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnKv()) + if err != nil { + return fmt.Errorf("error on reading cdn_kv %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnKvUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnKv()) + if err != nil { + return fmt.Errorf("error on updating cdn_kv %q, %s", d.Id(), err) + } + return resourceByteplusCdnKvRead(d, meta) +} + +func resourceByteplusCdnKvDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnKv()) + if err != nil { + return fmt.Errorf("error on deleting cdn_kv %q, %s", d.Id(), err) + } + return err +} + +var cdnKvImporter = func(data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + items := strings.Split(data.Id(), ":") + if len(items) != 3 { + return []*schema.ResourceData{data}, fmt.Errorf("import id must split with ':'") + } + if err := data.Set("namespace_id", items[0]); err != nil { + return []*schema.ResourceData{data}, err + } + if err := data.Set("namespace", items[1]); err != nil { + return []*schema.ResourceData{data}, err + } + if err := data.Set("key", items[2]); err != nil { + return []*schema.ResourceData{data}, err + } + return []*schema.ResourceData{data}, nil +} diff --git a/byteplus/cdn/cdn_kv/service_byteplus_cdn_kv.go b/byteplus/cdn/cdn_kv/service_byteplus_cdn_kv.go new file mode 100644 index 0000000..08f36f7 --- /dev/null +++ b/byteplus/cdn/cdn_kv/service_byteplus_cdn_kv.go @@ -0,0 +1,300 @@ +package cdn_kv + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnKvService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnKvService(c *bp.SdkClient) *ByteplusCdnKvService { + return &ByteplusCdnKvService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnKvService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnKvService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + data, err = bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListKvKey" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.NamespaceKeys", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.NamespaceKeys is not Slice") + } + return data, err + }) + + for _, v := range data { + namespaceKey, ok := v.(map[string]interface{}) + if !ok { + return data, fmt.Errorf(" The Sparrow of Result is not map ") + } + + // 查询 value + valueAction := "GetKeyValue" + valueReq := map[string]interface{}{ + "NamespaceId": namespaceKey["NamespaceId"], + "Namespace": namespaceKey["Namespace"], + "Key": namespaceKey["Key"], + } + logger.Debug(logger.ReqFormat, valueAction, valueReq) + valueResp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(valueAction), &valueReq) + if err != nil { + return data, err + } + logger.Debug(logger.RespFormat, valueAction, valueResp) + value, err := bp.ObtainSdkValue("Result.Value", *valueResp) + if err != nil { + return data, err + } + namespaceKey["Value"] = value + } + + return data, err +} + +func (s *ByteplusCdnKvService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + ids := strings.Split(id, ":") + if len(ids) != 3 { + return data, fmt.Errorf(" Invalid CdnKvNamespaceKey Id %s ", id) + } + + req := map[string]interface{}{ + "NamespaceId": ids[0], + "Namespace": ids[1], + } + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + var namespaceKey map[string]interface{} + if namespaceKey, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if namespaceKey["Key"].(string) == ids[2] { + data = namespaceKey + break + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_kv %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnKvService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{} +} + +func (ByteplusCdnKvService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, map[string]bp.ResponseConvert{ + "DDL": { + TargetField: "ddl", + }, + }, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnKvService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateKeyValue", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "ttl": { + TargetField: "TTL", + }, + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + namespaceId := d.Get("namespace_id").(string) + namespace := d.Get("namespace").(string) + key := d.Get("key").(string) + d.SetId(fmt.Sprintf(namespaceId + ":" + namespace + ":" + key)) + return nil + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateKeyValue", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "value": { + TargetField: "Value", + }, + "ttl": { + TargetField: "TTL", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if len(*call.SdkParam) > 0 { + ids := strings.Split(d.Id(), ":") + if len(ids) != 3 { + return false, fmt.Errorf(" Invalid CdnKvNamespaceKey Id %s ", d.Id()) + } + + (*call.SdkParam)["NamespaceId"] = ids[0] + (*call.SdkParam)["Namespace"] = ids[1] + (*call.SdkParam)["Key"] = ids[2] + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteKvKey", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + ids := strings.Split(d.Id(), ":") + if len(ids) != 3 { + return false, fmt.Errorf(" Invalid CdnKvNamespaceKey Id %s ", d.Id()) + } + + (*call.SdkParam)["NamespaceId"] = ids[0] + (*call.SdkParam)["Namespace"] = ids[1] + (*call.SdkParam)["Key"] = ids[2] + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading kv namespace key on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + NameField: "Key", + CollectField: "namespace_keys", + ResponseConverts: map[string]bp.ResponseConvert{ + "DDL": { + TargetField: "ddl", + }, + }, + } +} + +func (s *ByteplusCdnKvService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_kv_namespace/data_source_byteplus_cdn_kv_namespaces.go b/byteplus/cdn/cdn_kv_namespace/data_source_byteplus_cdn_kv_namespaces.go new file mode 100644 index 0000000..5057887 --- /dev/null +++ b/byteplus/cdn/cdn_kv_namespace/data_source_byteplus_cdn_kv_namespaces.go @@ -0,0 +1,86 @@ +package cdn_kv_namespace + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnKvNamespaces() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnKvNamespacesRead, + Schema: map[string]*schema.Schema{ + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + + "kv_namespaces": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the kv namespace.", + }, + "namespace_id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of the kv namespace.", + }, + "namespace": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the kv namespace.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the kv namespace.", + }, + "project_name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the project to which the namespace belongs, defaulting to `default`.", + }, + "creator": { + Type: schema.TypeString, + Computed: true, + Description: "The creator of the kv namespace.", + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The creation time of the kv namespace. Displayed in UNIX timestamp format.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "The update time of the kv namespace. Displayed in UNIX timestamp format.", + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnKvNamespacesRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnKvNamespaceService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnKvNamespaces()) +} diff --git a/byteplus/cdn/cdn_kv_namespace/resource_byteplus_cdn_kv_namespace.go b/byteplus/cdn/cdn_kv_namespace/resource_byteplus_cdn_kv_namespace.go new file mode 100644 index 0000000..12d81a9 --- /dev/null +++ b/byteplus/cdn/cdn_kv_namespace/resource_byteplus_cdn_kv_namespace.go @@ -0,0 +1,91 @@ +package cdn_kv_namespace + +import ( + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnKvNamespace can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_kv_namespace.default resource_id +``` + +*/ + +func ResourceByteplusCdnKvNamespace() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnKvNamespaceCreate, + Read: resourceByteplusCdnKvNamespaceRead, + Update: resourceByteplusCdnKvNamespaceUpdate, + Delete: resourceByteplusCdnKvNamespaceDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "namespace": { + Type: schema.TypeString, + Required: true, + Description: "Set a recognizable name for the namespace. The input requirements are as follows:\nLength should be between 2 and 64 characters.\nIt can only contain English letters, numbers, hyphens (-), and underscores (_).", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Set a description for the namespace. The input requirements are as follows:\nAny characters are allowed.\nThe length should not exceed 80 characters.", + }, + "project_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The name of the project to which the namespace belongs, defaulting to `default`.", + }, + }, + } + return resource +} + +func resourceByteplusCdnKvNamespaceCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvNamespaceService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnKvNamespace()) + if err != nil { + return fmt.Errorf("error on creating cdn_kv_namespace %q, %s", d.Id(), err) + } + return resourceByteplusCdnKvNamespaceRead(d, meta) +} + +func resourceByteplusCdnKvNamespaceRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvNamespaceService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnKvNamespace()) + if err != nil { + return fmt.Errorf("error on reading cdn_kv_namespace %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnKvNamespaceUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvNamespaceService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnKvNamespace()) + if err != nil { + return fmt.Errorf("error on updating cdn_kv_namespace %q, %s", d.Id(), err) + } + return resourceByteplusCdnKvNamespaceRead(d, meta) +} + +func resourceByteplusCdnKvNamespaceDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnKvNamespaceService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnKvNamespace()) + if err != nil { + return fmt.Errorf("error on deleting cdn_kv_namespace %q, %s", d.Id(), err) + } + return err +} diff --git a/byteplus/cdn/cdn_kv_namespace/service_byteplus_cdn_kv_namespace.go b/byteplus/cdn/cdn_kv_namespace/service_byteplus_cdn_kv_namespace.go new file mode 100644 index 0000000..d40e5d2 --- /dev/null +++ b/byteplus/cdn/cdn_kv_namespace/service_byteplus_cdn_kv_namespace.go @@ -0,0 +1,272 @@ +package cdn_kv_namespace + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnKvNamespaceService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnKvNamespaceService(c *bp.SdkClient) *ByteplusCdnKvNamespaceService { + return &ByteplusCdnKvNamespaceService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnKvNamespaceService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnKvNamespaceService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageOffsetQuery(m, "Limit", "Page", 50, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "ListKvNamespace" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Namespaces", *resp) + if err != nil { + return data, err + } + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Namespaces is not Slice") + } + return data, err + }) +} + +func (s *ByteplusCdnKvNamespaceService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + + req := map[string]interface{}{} + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + var namespace map[string]interface{} + if namespace, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + if namespace["NamespaceId"].(string) == id { + data = namespace + break + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_kv_namespace %s not exist ", id) + } + return data, err +} + +func (s *ByteplusCdnKvNamespaceService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{} +} + +func (ByteplusCdnKvNamespaceService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnKvNamespaceService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateKvNamespace", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{}, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + namespace := d.Get("namespace") + + // 接口未返回 Id,通过查询获得 + req := map[string]interface{}{} + results, err := s.ReadResources(req) + if err != nil { + return fmt.Errorf(" CreateKvNamespace AfterCall Error: %v ", err) + } + var ( + namespaceMap map[string]interface{} + ok bool + ) + for _, v := range results { + if namespaceMap, ok = v.(map[string]interface{}); !ok { + return errors.New("CreateKvNamespace AfterCall Error: Value is not map ") + } + if namespaceMap["Namespace"] == namespace { + d.SetId(namespaceMap["NamespaceId"].(string)) + break + } + } + + return nil + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvNamespaceService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateKvNamespace", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "description": { + TargetField: "Description", + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if d.HasChange("namespace") { + oldName, newName := d.GetChange("namespace") + (*call.SdkParam)["Namespace"] = oldName + (*call.SdkParam)["NewNamespace"] = newName + } else { + (*call.SdkParam)["Namespace"] = d.Get("namespace") + } + (*call.SdkParam)["NamespaceId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvNamespaceService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteKvNamespace", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "NamespaceId": resourceData.Id(), + "Namespace": resourceData.Get("namespace"), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getPostUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading kv namespace on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnKvNamespaceService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + NameField: "Namespace", + IdField: "NamespaceId", + CollectField: "kv_namespaces", + ResponseConverts: map[string]bp.ResponseConvert{ + "NamespaceId": { + TargetField: "id", + KeepDefault: true, + }, + }, + } +} + +func (s *ByteplusCdnKvNamespaceService) ReadResourceId(id string) string { + return id +} + +func (s *ByteplusCdnKvNamespaceService) ProjectTrn() *bp.ProjectTrn { + return &bp.ProjectTrn{ + ServiceName: "CDN", + ResourceType: "kv_namespace", + ProjectResponseField: "ProjectName", + ProjectSchemaField: "project_name", + } +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.GET, + ContentType: bp.Default, + Action: actionName, + } +} + +func getPostUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} diff --git a/byteplus/cdn/cdn_service_template/data_source_byteplus_cdn_service_templates.go b/byteplus/cdn/cdn_service_template/data_source_byteplus_cdn_service_templates.go new file mode 100644 index 0000000..126796b --- /dev/null +++ b/byteplus/cdn/cdn_service_template/data_source_byteplus_cdn_service_templates.go @@ -0,0 +1,170 @@ +package cdn_service_template + +import ( + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func DataSourceByteplusCdnServiceTemplates() *schema.Resource { + return &schema.Resource{ + Read: dataSourceByteplusCdnServiceTemplatesRead, + Schema: map[string]*schema.Schema{ + "filters": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fuzzy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Indicates the matching method. This parameter can take the following values: " + + "true: Indicates fuzzy matching. A policy is considered to meet the filtering criteria if the corresponding value of Name contains any value in the Value array. " + + "false: Indicates exact matching. A policy is considered to meet the filtering criteria if the corresponding value of Name matches any value in the Value array. " + + "Moreover, the Fuzzy value you can specify is affected by the Name value. See the description of Name. " + + "The default value of this parameter is false. " + + "Note that the matching process is case-sensitive.", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Represents the filtering type. This parameter can take the following values: " + + "Title: Filters policies by name. " + + "Id: Filters policies by ID. For this parameter value, the value of Fuzzy can only be false. " + + "Domain: Filters policies by the bound domain name. " + + "Type: Filters policies by type. For this parameter value, the value of Fuzzy can only be false. " + + "Status: Filters policies by status. For this parameter value, the value of Fuzzy can only be false. " + + "You can specify multiple filtering criteria simultaneously, but the Name in different filtering criteria cannot be the same.", + }, + "value": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Description: "Represents the values corresponding to Name, which is an array. " + + "When Name is Title, Id, or Domain, each value in the Value array should not exceed 100 characters in length. " + + "When Name is Type, the Value array can include one or more of the following values: " + + "cipher: Indicates a encryption policy. " + + "service: Indicates a delivery policy. " + + "When Name is Status, the Value array can include one or more of the following values: " + + "locked: Indicates the status is \"published\". " + + "editing: Indicates the status is \"draft\". " + + "When Fuzzy is false, you can specify multiple values in the array. " + + "When Fuzzy is true, you can only specify one value in the array.", + }, + }, + }, + Description: "Indicates a set of filtering criteria used to obtain a list of policies that meet these criteria. " + + "If you do not specify any filtering criteria, this API returns all policies under your account. " + + "Multiple filtering criteria are related by AND, meaning only policies that meet all filtering criteria will be included in the list returned by this API. " + + "In the API response, the actual policies returned are affected by PageNum and PageSize.", + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, + Description: "A Name Regex of Resource.", + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + Description: "File name where to save data source results.", + }, + "total_count": { + Type: schema.TypeInt, + Computed: true, + Description: "The total count of query.", + }, + "templates": { + Description: "The collection of query.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bound_domains": { + Type: schema.TypeList, + Computed: true, + Description: "Represents a list of domain names bound to the policy specified by TemplateId. " + + "If the policy is not bound to any domain names, the value of this parameter is null.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bound_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the time when the policy was bound to the domain name specified by Domain, in Unix timestamp format.", + }, + "domain": { + Type: schema.TypeString, + Computed: true, + Description: "Represents one of the domain names bound to the policy.", + }, + }, + }, + }, + "create_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the creation time of the policy, in Unix timestamp format.", + }, + "message": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the description of the policy.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the status of the policy. This parameter can take the following values: " + + "locked: Indicates the status is \"published\". " + + "editing: Indicates the status is \"draft\".", + }, + "template_id": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the ID of a policy in the list of policies returned by the API.", + }, + "title": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the name of the policy.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the type of the policy. This parameter can take the following values: " + + "cipher: Indicates an encryption policy. " + + "service: Indicates a distribution policy.", + }, + "update_time": { + Type: schema.TypeInt, + Computed: true, + Description: "Indicates the last modification time of the policy, in Unix timestamp format. " + + "If the policy has not been updated since its creation, the value of this parameter is the same as CreateTime.", + }, + "exception": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether the policy includes special configurations. " + + "Special configurations refer to those not operated by users but by BytePlus engineers. " + + "This parameter can take the following values:" + + " true: Indicates it includes special configurations. " + + "false: Indicates it does not include special configurations.", + }, + "project": { + Type: schema.TypeString, + Computed: true, + Description: "Indicates the project to which the policy belongs.", + }, + }, + }, + }, + }, + } +} + +func dataSourceByteplusCdnServiceTemplatesRead(d *schema.ResourceData, meta interface{}) error { + service := NewCdnServiceTemplateService(meta.(*bp.SdkClient)) + return service.Dispatcher.Data(service, d, DataSourceByteplusCdnServiceTemplates()) +} diff --git a/byteplus/cdn/cdn_service_template/resource_byteplus_cdn_service_template.go b/byteplus/cdn/cdn_service_template/resource_byteplus_cdn_service_template.go new file mode 100644 index 0000000..ff30d54 --- /dev/null +++ b/byteplus/cdn/cdn_service_template/resource_byteplus_cdn_service_template.go @@ -0,0 +1,107 @@ +package cdn_service_template + +import ( + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +/* + +Import +CdnServiceTemplate can be imported using the id, e.g. +``` +$ terraform import byteplus_cdn_service_template.default resource_id +``` + +*/ + +func ResourceByteplusCdnServiceTemplate() *schema.Resource { + resource := &schema.Resource{ + Create: resourceByteplusCdnServiceTemplateCreate, + Read: resourceByteplusCdnServiceTemplateRead, + Update: resourceByteplusCdnServiceTemplateUpdate, + Delete: resourceByteplusCdnServiceTemplateDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + Description: "Indicates the name of the encryption policy you want to create. The name must not exceed 100 characters.", + }, + "message": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates the description of the encryption policy, which must not exceed 120 characters.", + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Indicates the project to which this encryption policy belongs. The default value of the parameter is default, indicating the Default project.", + }, + "service_template_config": { + Type: schema.TypeString, + Required: true, + Description: "The service template configuration. " + + "Please convert the configuration module structure into json and pass it into a string. " + + "You must specify the Origin module. The OriginProtocol parameter, and other domain configuration modules are optional. " + + "For detailed parameter introduction, please refer to `https://docs.byteplus.com/en/docs/byteplus-cdn/reference-updateservicetemplate`.", + }, + "lock_template": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether to lock the template. " + + "If you set this field to true, then the template will be locked. Please note that the template cannot be modified or unlocked after it is locked. " + + "When you want to use this template to create a domain name, please lock the template first. The default value is false.", + }, + }, + } + return resource +} + +func resourceByteplusCdnServiceTemplateCreate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnServiceTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Create(service, d, ResourceByteplusCdnServiceTemplate()) + if err != nil { + return fmt.Errorf("error on creating cdn_service_template %q, %s", d.Id(), err) + } + return resourceByteplusCdnServiceTemplateRead(d, meta) +} + +func resourceByteplusCdnServiceTemplateRead(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnServiceTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Read(service, d, ResourceByteplusCdnServiceTemplate()) + if err != nil { + return fmt.Errorf("error on reading cdn_service_template %q, %s", d.Id(), err) + } + return err +} + +func resourceByteplusCdnServiceTemplateUpdate(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnServiceTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Update(service, d, ResourceByteplusCdnServiceTemplate()) + if err != nil { + return fmt.Errorf("error on updating cdn_service_template %q, %s", d.Id(), err) + } + return resourceByteplusCdnServiceTemplateRead(d, meta) +} + +func resourceByteplusCdnServiceTemplateDelete(d *schema.ResourceData, meta interface{}) (err error) { + service := NewCdnServiceTemplateService(meta.(*bp.SdkClient)) + err = service.Dispatcher.Delete(service, d, ResourceByteplusCdnServiceTemplate()) + if err != nil { + return fmt.Errorf("error on deleting cdn_service_template %q, %s", d.Id(), err) + } + return err +} diff --git a/byteplus/cdn/cdn_service_template/service_byteplus_cdn_service_template.go b/byteplus/cdn/cdn_service_template/service_byteplus_cdn_service_template.go new file mode 100644 index 0000000..94b6ac5 --- /dev/null +++ b/byteplus/cdn/cdn_service_template/service_byteplus_cdn_service_template.go @@ -0,0 +1,396 @@ +package cdn_service_template + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + bp "github.com/byteplus-sdk/terraform-provider-byteplus/common" + "github.com/byteplus-sdk/terraform-provider-byteplus/logger" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type ByteplusCdnServiceTemplateService struct { + Client *bp.SdkClient + Dispatcher *bp.Dispatcher +} + +func NewCdnServiceTemplateService(c *bp.SdkClient) *ByteplusCdnServiceTemplateService { + return &ByteplusCdnServiceTemplateService{ + Client: c, + Dispatcher: &bp.Dispatcher{}, + } +} + +func (s *ByteplusCdnServiceTemplateService) GetClient() *bp.SdkClient { + return s.Client +} + +func (s *ByteplusCdnServiceTemplateService) ReadResources(m map[string]interface{}) (data []interface{}, err error) { + var ( + resp *map[string]interface{} + results interface{} + ok bool + ) + return bp.WithPageNumberQuery(m, "PageSize", "PageNum", 100, 1, func(condition map[string]interface{}) ([]interface{}, error) { + action := "DescribeTemplates" + + bytes, _ := json.Marshal(condition) + logger.Debug(logger.ReqFormat, action, string(bytes)) + if condition == nil { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), nil) + if err != nil { + return data, err + } + } else { + resp, err = s.Client.UniversalClient.DoCall(getUniversalInfo(action), &condition) + if err != nil { + return data, err + } + } + respBytes, _ := json.Marshal(resp) + logger.Debug(logger.RespFormat, action, condition, string(respBytes)) + + results, err = bp.ObtainSdkValue("Result.Templates", *resp) + if err != nil { + return data, err + } + + if results == nil { + results = []interface{}{} + } + if data, ok = results.([]interface{}); !ok { + return data, errors.New("Result.Templates is not Slice") + } + + return data, err + }) +} + +func (s *ByteplusCdnServiceTemplateService) ReadResource(resourceData *schema.ResourceData, id string) (data map[string]interface{}, err error) { + var ( + results []interface{} + ok bool + ) + if id == "" { + id = s.ReadResourceId(resourceData.Id()) + } + filter := map[string]interface{}{ + "Fuzzy": false, + "Name": "Id", + "Value": []string{id}, + } + req := map[string]interface{}{ + "Filters": []interface{}{filter}, + } + + results, err = s.ReadResources(req) + if err != nil { + return data, err + } + for _, v := range results { + if data, ok = v.(map[string]interface{}); !ok { + return data, errors.New("Value is not map ") + } + } + if len(data) == 0 { + return data, fmt.Errorf("cdn_service_template %s not exist ", id) + } + status := data["Status"].(string) + if status == "locked" { + data["LockTemplate"] = true + } else if status == "editing" { + data["LockTemplate"] = false + } + return data, err +} + +func (s *ByteplusCdnServiceTemplateService) RefreshResourceState(resourceData *schema.ResourceData, target []string, timeout time.Duration, id string) *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{}, + Delay: 1 * time.Second, + MinTimeout: 1 * time.Second, + Target: target, + Timeout: timeout, + Refresh: func() (result interface{}, state string, err error) { + var ( + d map[string]interface{} + status interface{} + failStates []string + ) + failStates = append(failStates, "Failed") + d, err = s.ReadResource(resourceData, id) + if err != nil { + return nil, "", err + } + status, err = bp.ObtainSdkValue("Status", d) + if err != nil { + return nil, "", err + } + for _, v := range failStates { + if v == status.(string) { + return nil, "", fmt.Errorf("cdn_service_template status error, status: %s", status.(string)) + } + } + return d, status.(string), err + }, + } +} + +func (s *ByteplusCdnServiceTemplateService) CreateResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "CreateServiceTemplate", + ConvertMode: bp.RequestConvertAll, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "service_template_config": { + Ignore: true, + }, + "lock_template": { + Ignore: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + var ( + config map[string]interface{} + ) + tmpConfig, ok := d.Get("service_template_config").(string) + if !ok { + return false, errors.New("service_template_config is not a map") + } + err := json.Unmarshal([]byte(tmpConfig), &config) + if err != nil || len(config) == 0 { + return false, errors.New("service_template_config struct err or service_template_config is empty") + } + for k, v := range config { + (*call.SdkParam)[k] = v + } + return true, nil + + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + id, _ := bp.ObtainSdkValue("Result.TemplateId", *resp) + d.SetId(id.(string)) + return nil + }, + Refresh: &bp.StateRefresh{ + Target: []string{"editing"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, callback) + if resourceData.Get("lock_template").(bool) { + lockCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "LockTemplate", + ContentType: bp.ContentTypeJson, + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + (*call.SdkParam)["TemplateId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"locked"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, lockCallback) + } + return callbacks +} + +func (ByteplusCdnServiceTemplateService) WithResourceResponseHandlers(d map[string]interface{}) []bp.ResourceResponseHandler { + handler := func() (map[string]interface{}, map[string]bp.ResponseConvert, error) { + return d, nil, nil + } + return []bp.ResourceResponseHandler{handler} +} + +func (s *ByteplusCdnServiceTemplateService) ModifyResource(resourceData *schema.ResourceData, resource *schema.Resource) []bp.Callback { + var callbacks []bp.Callback + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "UpdateServiceTemplate", + ConvertMode: bp.RequestConvertInConvert, + ContentType: bp.ContentTypeJson, + Convert: map[string]bp.RequestConvert{ + "lock_template": { + Ignore: true, + }, + "title": { + TargetField: "Title", + ForceGet: true, + }, + "message": { + TargetField: "Message", + ForceGet: true, + }, + "service_template_config": { + Ignore: true, + }, + "project": { + Ignore: true, + }, + }, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if d.HasChanges("title", "message", "service_template_config") { + (*call.SdkParam)["TemplateId"] = d.Id() + var ( + config map[string]interface{} + ) + tmpConfig, ok := d.Get("service_template_config").(string) + if !ok { + return false, errors.New("service_template_config is not a map") + } + err := json.Unmarshal([]byte(tmpConfig), &config) + if err != nil || len(config) == 0 { + return false, errors.New("service_template_config struct err or service_template_config is empty") + } + for k, v := range config { + (*call.SdkParam)[k] = v + } + return true, nil + } + return false, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.ReqFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + }, + } + callbacks = append(callbacks, callback) + if resourceData.HasChange("lock_template") { + lockCallback := bp.Callback{ + Call: bp.SdkCall{ + Action: "LockTemplate", + ContentType: bp.ContentTypeJson, + ConvertMode: bp.RequestConvertIgnore, + BeforeCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (bool, error) { + if !d.Get("lock_template").(bool) { + // 不允许解锁 + return false, fmt.Errorf("Template cannot unlock. ") + } + (*call.SdkParam)["TemplateId"] = d.Id() + return true, nil + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + resp, err := s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + logger.Debug(logger.RespFormat, call.Action, resp, err) + return resp, err + }, + Refresh: &bp.StateRefresh{ + Target: []string{"locked"}, + Timeout: resourceData.Timeout(schema.TimeoutCreate), + }, + }, + } + callbacks = append(callbacks, lockCallback) + } + return callbacks +} + +func (s *ByteplusCdnServiceTemplateService) RemoveResource(resourceData *schema.ResourceData, r *schema.Resource) []bp.Callback { + callback := bp.Callback{ + Call: bp.SdkCall{ + Action: "DeleteTemplate", + ConvertMode: bp.RequestConvertIgnore, + ContentType: bp.ContentTypeJson, + SdkParam: &map[string]interface{}{ + "TemplateId": resourceData.Id(), + }, + ExecuteCall: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall) (*map[string]interface{}, error) { + logger.Debug(logger.RespFormat, call.Action, call.SdkParam) + return s.Client.UniversalClient.DoCall(getUniversalInfo(call.Action), call.SdkParam) + }, + AfterCall: func(d *schema.ResourceData, client *bp.SdkClient, resp *map[string]interface{}, call bp.SdkCall) error { + return bp.CheckResourceUtilRemoved(d, s.ReadResource, 5*time.Minute) + }, + CallError: func(d *schema.ResourceData, client *bp.SdkClient, call bp.SdkCall, baseErr error) error { + //出现错误后重试 + return resource.Retry(15*time.Minute, func() *resource.RetryError { + _, callErr := s.ReadResource(d, "") + if callErr != nil { + if bp.ResourceNotFoundError(callErr) { + return nil + } else { + return resource.NonRetryableError(fmt.Errorf("error on reading cdn service template on delete %q, %w", d.Id(), callErr)) + } + } + _, callErr = call.ExecuteCall(d, client, call) + if callErr == nil { + return nil + } + return resource.RetryableError(callErr) + }) + }, + }, + } + return []bp.Callback{callback} +} + +func (s *ByteplusCdnServiceTemplateService) DatasourceResources(*schema.ResourceData, *schema.Resource) bp.DataSourceInfo { + return bp.DataSourceInfo{ + RequestConverts: map[string]bp.RequestConvert{ + "filters": { + TargetField: "Filters", + ConvertType: bp.ConvertJsonObjectArray, + NextLevelConvert: map[string]bp.RequestConvert{ + "value": { + TargetField: "Value", + ConvertType: bp.ConvertJsonArray, + }, + }, + }, + }, + NameField: "Title", + IdField: "TemplateId", + CollectField: "templates", + ContentType: bp.ContentTypeJson, + ResponseConverts: map[string]bp.ResponseConvert{}, + } +} + +func (s *ByteplusCdnServiceTemplateService) ReadResourceId(id string) string { + return id +} + +func getUniversalInfo(actionName string) bp.UniversalInfo { + return bp.UniversalInfo{ + ServiceName: "CDN", + Version: "2021-03-01", + HttpMethod: bp.POST, + ContentType: bp.ApplicationJSON, + Action: actionName, + } +} + +func (s *ByteplusCdnServiceTemplateService) ProjectTrn() *bp.ProjectTrn { + return &bp.ProjectTrn{ + ServiceName: "CDN", + ResourceType: "template", + ProjectResponseField: "Project", + ProjectSchemaField: "project", + } +} diff --git a/byteplus/provider.go b/byteplus/provider.go index e4936d5..1c672b8 100644 --- a/byteplus/provider.go +++ b/byteplus/provider.go @@ -30,6 +30,18 @@ import ( "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/autoscaling/scaling_policy" "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/bandwidth_package/bandwidth_package" "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/bandwidth_package/bandwidth_package_attachment" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_certificate" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_cipher_template" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_cron_job" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_cron_job_state" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_domain" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_domain_enabler" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_edge_function" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_edge_function_associate" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_edge_function_publish" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_kv" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_kv_namespace" + "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/cdn/cdn_service_template" "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/clb/acl" "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/clb/acl_entry" "github.com/byteplus-sdk/terraform-provider-byteplus/byteplus/clb/certificate" @@ -256,6 +268,17 @@ func Provider() terraform.ResourceProvider { "byteplus_rds_postgresql_accounts": rds_postgresql_account.DataSourceByteplusRdsPostgresqlAccounts(), "byteplus_rds_postgresql_instances": rds_postgresql_instance.DataSourceByteplusRdsPostgresqlInstances(), "byteplus_rds_postgresql_schemas": rds_postgresql_schema.DataSourceByteplusRdsPostgresqlSchemas(), + + // ================ CDN ================ + "byteplus_cdn_domains": cdn_domain.DataSourceByteplusCdnDomains(), + "byteplus_cdn_cipher_templates": cdn_cipher_template.DataSourceByteplusCdnCipherTemplates(), + "byteplus_cdn_service_templates": cdn_service_template.DataSourceByteplusCdnServiceTemplates(), + "byteplus_cdn_certificates": cdn_certificate.DataSourceByteplusCdnCertificates(), + "byteplus_cdn_edge_functions": cdn_edge_function.DataSourceByteplusCdnEdgeFunctions(), + "byteplus_cdn_edge_function_publishes": cdn_edge_function_publish.DataSourceByteplusCdnEdgeFunctionPublishs(), + "byteplus_cdn_cron_jobs": cdn_cron_job.DataSourceByteplusCdnCronJobs(), + "byteplus_cdn_kv_namespaces": cdn_kv_namespace.DataSourceByteplusCdnKvNamespaces(), + "byteplus_cdn_kvs": cdn_kv.DataSourceByteplusCdnKvs(), }, ResourcesMap: map[string]*schema.Resource{ // ================ ECS ================ @@ -333,6 +356,20 @@ func Provider() terraform.ResourceProvider { "byteplus_rds_postgresql_instance": rds_postgresql_instance.ResourceByteplusRdsPostgresqlInstance(), "byteplus_rds_postgresql_instance_readonly_node": rds_postgresql_instance_readonly_node.ResourceByteplusRdsPostgresqlInstanceReadonlyNode(), "byteplus_rds_postgresql_schema": rds_postgresql_schema.ResourceByteplusRdsPostgresqlSchema(), + + // ================ CDN ================ + "byteplus_cdn_domain": cdn_domain.ResourceByteplusCdnDomain(), + "byteplus_cdn_cipher_template": cdn_cipher_template.ResourceByteplusCdnCipherTemplate(), + "byteplus_cdn_service_template": cdn_service_template.ResourceByteplusCdnServiceTemplate(), + "byteplus_cdn_domain_enabler": cdn_domain_enabler.ResourceByteplusCdnDomainEnabler(), + "byteplus_cdn_certificate": cdn_certificate.ResourceByteplusCdnCertificate(), + "byteplus_cdn_edge_function": cdn_edge_function.ResourceByteplusCdnEdgeFunction(), + "byteplus_cdn_edge_function_publish": cdn_edge_function_publish.ResourceByteplusCdnEdgeFunctionPublish(), + "byteplus_cdn_edge_function_associate": cdn_edge_function_associate.ResourceByteplusCdnEdgeFunctionAssociate(), + "byteplus_cdn_cron_job": cdn_cron_job.ResourceByteplusCdnCronJob(), + "byteplus_cdn_cron_job_state": cdn_cron_job_state.ResourceByteplusCdnCronJobState(), + "byteplus_cdn_kv_namespace": cdn_kv_namespace.ResourceByteplusCdnKvNamespace(), + "byteplus_cdn_kv": cdn_kv.ResourceByteplusCdnKv(), }, ConfigureFunc: ProviderConfigure, } diff --git a/common/common_byteplus_project.go b/common/common_byteplus_project.go index ffcc572..e5db4ba 100644 --- a/common/common_byteplus_project.go +++ b/common/common_byteplus_project.go @@ -62,8 +62,19 @@ func (p *Project) ModifyProject(trn *ProjectTrn, resourceData *schema.ResourceDa if err != nil { return false, err } - trnStr := fmt.Sprintf("trn:%s:%s:%d:%s/%s", trn.ServiceName, p.Client.Region, int(accountId.(float64)), - trn.ResourceType, id) + var trnStr string + if trn.ServiceName == "tos" && trn.ResourceType == "bucket" { + // tos bucket 特殊处理 + trnStr = fmt.Sprintf("trn:%s:%s:%d:%s", trn.ServiceName, p.Client.Region, int(accountId.(float64)), id) + } else if (trn.ServiceName == "transitrouter" && trn.ResourceType == "transitrouterbandwidthpackage") || + (trn.ServiceName == "CDN" && (trn.ResourceType == "template" || trn.ResourceType == "function" || trn.ResourceType == "kv_namespace")) { + // transit router bandwidth package & cdn 特殊处理 + trnStr = fmt.Sprintf("trn:%s:%s:%d:%s/%s", trn.ServiceName, "", int(accountId.(float64)), + trn.ResourceType, id) + } else { + trnStr = fmt.Sprintf("trn:%s:%s:%d:%s/%s", trn.ServiceName, p.Client.Region, int(accountId.(float64)), + trn.ResourceType, id) + } (*call.SdkParam)["ResourceTrn.1"] = trnStr return true, nil }, diff --git a/common/common_byteplus_version.go b/common/common_byteplus_version.go index a47055a..6af6c57 100644 --- a/common/common_byteplus_version.go +++ b/common/common_byteplus_version.go @@ -2,5 +2,5 @@ package common const ( TerraformProviderName = "terraform-provider-byteplus" - TerraformProviderVersion = "0.0.4" + TerraformProviderVersion = "0.0.5" ) diff --git a/docgen/main.go b/docgen/main.go index 0e3ff86..65085a0 100644 --- a/docgen/main.go +++ b/docgen/main.go @@ -134,6 +134,7 @@ var resourceKeys = map[string]string{ "direct_connect": "DIRECT_CONNECT", "bandwidth_package": "BANDWIDTH_PACKAGE", "rds_postgresql": "RDS_POSTGRESQL", + "cdn": "CDN", } type Products struct { diff --git a/example/cdnCertificate/main.tf b/example/cdnCertificate/main.tf new file mode 100644 index 0000000..3fb232a --- /dev/null +++ b/example/cdnCertificate/main.tf @@ -0,0 +1,6 @@ +resource "byteplus_cdn_certificate" "foo" { + certificate = "-----BEGIN CERTIFICATE----- *** -----END CERTIFICATE-----" + private_key = "-----BEGIN PRIVATE KEY----- *** -----END PRIVATE KEY-----" + desc = "tf-test" + repeatable = true +} diff --git a/example/cdnCipherTemplate/main.tf b/example/cdnCipherTemplate/main.tf new file mode 100644 index 0000000..74c3f31 --- /dev/null +++ b/example/cdnCipherTemplate/main.tf @@ -0,0 +1,27 @@ +resource "byteplus_cdn_cipher_template" "foo" { + title = "tf-test" + message = "test for tf" + project = "test" + https { + disable_http = false + forced_redirect { + enable_forced_redirect = true + status_code = "302" + } + http2 = false + ocsp = false + tls_version = ["tlsv1.1", "tlsv1.2", "tlsv1.3"] + hsts { + subdomain = "exclude" + switch = true + ttl = 3600 + } + } + # http_forced_redirect { + # enable_forced_redirect = false + # status_code = "301" + # } + quic { + switch = false + } +} \ No newline at end of file diff --git a/example/cdnCronJob/main.tf b/example/cdnCronJob/main.tf new file mode 100644 index 0000000..1a509f0 --- /dev/null +++ b/example/cdnCronJob/main.tf @@ -0,0 +1,20 @@ +resource "byteplus_cdn_edge_function" "foo" { + name = "acc-test-function" + remark = "tf-test" + project_name = "default" + source_code = base64encode("hello world") + envs { + key = "k1" + value = "v1" + } + canary_countries = ["China", "Japan", "United Kingdom"] +} + +resource "byteplus_cdn_cron_job" "foo" { + function_id = byteplus_cdn_edge_function.foo.id + job_name = "acc-test-cron-job" + description = "tf-test" + cron_type = 1 + cron_expression = "0 17 10 * *" + parameter = "test" +} diff --git a/example/cdnCronJobState/main.tf b/example/cdnCronJobState/main.tf new file mode 100644 index 0000000..fd8b734 --- /dev/null +++ b/example/cdnCronJobState/main.tf @@ -0,0 +1,26 @@ +resource "byteplus_cdn_edge_function" "foo" { + name = "acc-test-function" + remark = "tf-test" + project_name = "default" + source_code = base64encode("hello world") + envs { + key = "k1" + value = "v1" + } + canary_countries = ["China", "Japan", "United Kingdom"] +} + +resource "byteplus_cdn_cron_job" "foo" { + function_id = byteplus_cdn_edge_function.foo.id + job_name = "acc-test-cron-job" + description = "tf-test" + cron_type = 1 + cron_expression = "0 17 10 * *" + parameter = "test" +} + +resource "byteplus_cdn_cron_job_state" "foo" { + function_id = byteplus_cdn_edge_function.foo.id + job_name = byteplus_cdn_cron_job.foo.job_name + action = "Start" +} diff --git a/example/cdnDomain/main.tf b/example/cdnDomain/main.tf new file mode 100644 index 0000000..8e4e3e9 --- /dev/null +++ b/example/cdnDomain/main.tf @@ -0,0 +1,70 @@ +resource "byteplus_cdn_service_template" "foo" { + title = "tf-test2" + message = "test2" + project = "" + # 是否发布模版 + lock_template = true + service_template_config = jsonencode( + { + OriginIpv6 = "followclient" + ConditionalOrigin = { + OriginRules = [] + } + Origin = [{ + OriginAction = { + OriginLines = [ + { + Address = "10.10.10.10" + HttpPort = "80" + HttpsPort = "443" + InstanceType = "ip" + OriginType = "primary" + Weight = "1" + } + ] + } + }] + OriginHost = "" + OriginProtocol = "http" + OriginHost = "" + } + ) +} + +resource "byteplus_cdn_cipher_template" "foo" { + title = "tf-test" + message = "test for tf" + project = "" + lock_template = true + https { + disable_http = false + forced_redirect { + enable_forced_redirect = true + status_code = "302" + } + http2 = false + ocsp = false + tls_version = ["tlsv1.1", "tlsv1.2", "tlsv1.3"] + hsts { + subdomain = "exclude" + switch = true + ttl = 3600 + } + } + # http_forced_redirect { + # enable_forced_redirect = false + # status_code = "301" + # } + quic { + switch = false + } +} + +resource "byteplus_cdn_domain" "foo" { + domain = "tf-test.com" + service_template_id = byteplus_cdn_service_template.foo.id + https_switch = "off" + cipher_template_id = byteplus_cdn_cipher_template.foo.id + project = "" + service_region = "outside_chinese_mainland" +} \ No newline at end of file diff --git a/example/cdnDomainEnabler/main.tf b/example/cdnDomainEnabler/main.tf new file mode 100644 index 0000000..6b89b59 --- /dev/null +++ b/example/cdnDomainEnabler/main.tf @@ -0,0 +1,75 @@ +resource "byteplus_cdn_service_template" "foo" { + title = "tf-test2" + message = "test2" + project = "" + # 是否发布模版 + lock_template = true + service_template_config = jsonencode( + { + OriginIpv6 = "followclient" + ConditionalOrigin = { + OriginRules = [] + } + Origin = [{ + OriginAction = { + OriginLines = [ + { + Address = "10.10.10.10" + HttpPort = "80" + HttpsPort = "443" + InstanceType = "ip" + OriginType = "primary" + Weight = "1" + } + ] + } + }] + OriginHost = "" + OriginProtocol = "http" + OriginHost = "" + } + ) +} + +resource "byteplus_cdn_cipher_template" "foo" { + title = "tf-test" + message = "test for tf" + project = "" + lock_template = true + https { + disable_http = false + forced_redirect { + enable_forced_redirect = true + status_code = "302" + } + http2 = false + ocsp = false + tls_version = ["tlsv1.1", "tlsv1.2", "tlsv1.3"] + hsts { + subdomain = "exclude" + switch = true + ttl = 3600 + } + } + # http_forced_redirect { + # enable_forced_redirect = false + # status_code = "301" + # } + quic { + switch = false + } +} + +resource "byteplus_cdn_domain" "foo" { + domain = "tf.byte-test.com" + service_template_id = byteplus_cdn_service_template.foo.id + https_switch = "on" + cert_id = "cert-" + cipher_template_id = byteplus_cdn_cipher_template.foo.id + project = "" + service_region = "outside_chinese_mainland" +} + +resource "byteplus_cdn_domain_enabler" "foo" { + domain = byteplus_cdn_domain.foo.id +} \ No newline at end of file diff --git a/example/cdnEdgeFunction/main.tf b/example/cdnEdgeFunction/main.tf new file mode 100644 index 0000000..1d7bd18 --- /dev/null +++ b/example/cdnEdgeFunction/main.tf @@ -0,0 +1,11 @@ +resource "byteplus_cdn_edge_function" "foo" { + name = "acc-test-function" + remark = "tf-test" + project_name = "default" + source_code = base64encode("hello world") + envs { + key = "k1" + value = "v1" + } + canary_countries = ["China", "Japan", "United Kingdom"] +} diff --git a/example/cdnEdgeFunctionAssociate/main.tf b/example/cdnEdgeFunctionAssociate/main.tf new file mode 100644 index 0000000..d0fea96 --- /dev/null +++ b/example/cdnEdgeFunctionAssociate/main.tf @@ -0,0 +1,16 @@ +resource "byteplus_cdn_edge_function" "foo" { + name = "acc-test-function" + remark = "tf-test" + project_name = "default" + source_code = base64encode("hello world") + envs { + key = "k1" + value = "v1" + } + canary_countries = ["China", "Japan", "United Kingdom"] +} + +resource "byteplus_cdn_edge_function_associate" "foo" { + function_id = byteplus_cdn_edge_function.foo.id + domain = "tf.com" +} diff --git a/example/cdnEdgeFunctionPublish/main.tf b/example/cdnEdgeFunctionPublish/main.tf new file mode 100644 index 0000000..48f74b3 --- /dev/null +++ b/example/cdnEdgeFunctionPublish/main.tf @@ -0,0 +1,17 @@ +resource "byteplus_cdn_edge_function" "foo" { + name = "acc-test-function" + remark = "tf-test" + project_name = "default" + source_code = base64encode("hello world") + envs { + key = "k1" + value = "v1" + } + canary_countries = ["China", "Japan", "United Kingdom"] +} + +resource "byteplus_cdn_edge_function_publish" "foo" { + function_id = byteplus_cdn_edge_function.foo.id + description = "test publish" + publish_action = "FullPublish" +} diff --git a/example/cdnKv/main.tf b/example/cdnKv/main.tf new file mode 100644 index 0000000..10b7caf --- /dev/null +++ b/example/cdnKv/main.tf @@ -0,0 +1,13 @@ +resource "byteplus_cdn_kv_namespace" "foo" { + namespace = "acc-test-kv-namespace" + description = "tf-test" + project_name = "default" +} + +resource "byteplus_cdn_kv" "foo" { + namespace_id = byteplus_cdn_kv_namespace.foo.id + namespace = byteplus_cdn_kv_namespace.foo.namespace + key = "acc-test-key" + value = base64encode("tf-test") + ttl = 1000 +} diff --git a/example/cdnKvNamespace/main.tf b/example/cdnKvNamespace/main.tf new file mode 100644 index 0000000..2c5673c --- /dev/null +++ b/example/cdnKvNamespace/main.tf @@ -0,0 +1,5 @@ +resource "byteplus_cdn_kv_namespace" "foo" { + namespace = "acc-test-kv-namespace" + description = "tf-test" + project_name = "default" +} diff --git a/example/cdnServiceTemplate/main.tf b/example/cdnServiceTemplate/main.tf new file mode 100644 index 0000000..f27c12f --- /dev/null +++ b/example/cdnServiceTemplate/main.tf @@ -0,0 +1,30 @@ +resource "byteplus_cdn_service_template" "foo" { + title = "tf-test2" + message = "test2" + project = "test" + service_template_config = jsonencode( + { + OriginIpv6 = "followclient" + ConditionalOrigin = { + OriginRules = [] + } + Origin = [{ + OriginAction = { + OriginLines = [ + { + Address = "10.10.10.10" + HttpPort = "80" + HttpsPort = "443" + InstanceType = "ip" + OriginType = "primary" + Weight = "1" + } + ] + } + }] + OriginHost = "" + OriginProtocol = "http" + OriginHost = "" + } + ) +} \ No newline at end of file diff --git a/example/dataCdnCertificates/main.tf b/example/dataCdnCertificates/main.tf new file mode 100644 index 0000000..39dd804 --- /dev/null +++ b/example/dataCdnCertificates/main.tf @@ -0,0 +1,6 @@ +data "byteplus_cdn_certificates" "foo" { + configured_domain = ["byteplus-demo.byte-test.com"] + name = "*.byte-test.com" + fuzzy_match = true + status = "running" +} diff --git a/example/dataCdnCipherTemplates/main.tf b/example/dataCdnCipherTemplates/main.tf new file mode 100644 index 0000000..29d6bb9 --- /dev/null +++ b/example/dataCdnCipherTemplates/main.tf @@ -0,0 +1 @@ +data "byteplus_cdn_cipher_templates" "foo" {} \ No newline at end of file diff --git a/example/dataCdnCronJobs/main.tf b/example/dataCdnCronJobs/main.tf new file mode 100644 index 0000000..4d307f4 --- /dev/null +++ b/example/dataCdnCronJobs/main.tf @@ -0,0 +1,3 @@ +data "byteplus_cdn_cron_jobs" "foo" { + function_id = "8f06f8db8d6b4bcdb979db68273f****" +} diff --git a/example/dataCdnDomains/main.tf b/example/dataCdnDomains/main.tf new file mode 100644 index 0000000..58f34e5 --- /dev/null +++ b/example/dataCdnDomains/main.tf @@ -0,0 +1 @@ +data "byteplus_cdn_domains" "foo" {} \ No newline at end of file diff --git a/example/dataCdnEdgeFunctionPublishes/main.tf b/example/dataCdnEdgeFunctionPublishes/main.tf new file mode 100644 index 0000000..5cc21dc --- /dev/null +++ b/example/dataCdnEdgeFunctionPublishes/main.tf @@ -0,0 +1,3 @@ +data "byteplus_cdn_edge_function_publishes" "foo" { + function_id = "8f06f8db8d6b4bcdb979db68273f****" +} diff --git a/example/dataCdnEdgeFunctions/main.tf b/example/dataCdnEdgeFunctions/main.tf new file mode 100644 index 0000000..f9316d5 --- /dev/null +++ b/example/dataCdnEdgeFunctions/main.tf @@ -0,0 +1,3 @@ +data "byteplus_cdn_edge_functions" "foo" { + status = 100 +} diff --git a/example/dataCdnKvNamespaces/main.tf b/example/dataCdnKvNamespaces/main.tf new file mode 100644 index 0000000..d9eca11 --- /dev/null +++ b/example/dataCdnKvNamespaces/main.tf @@ -0,0 +1,3 @@ +data "byteplus_cdn_kv_namespaces" "foo" { + +} diff --git a/example/dataCdnKvs/main.tf b/example/dataCdnKvs/main.tf new file mode 100644 index 0000000..500492f --- /dev/null +++ b/example/dataCdnKvs/main.tf @@ -0,0 +1,4 @@ +data "byteplus_cdn_kvs" "foo" { + namespace_id = "4723722642589338688" + namespace = "acc-test-kv-namespace" +} diff --git a/example/dataCdnServiceTemplates/main.tf b/example/dataCdnServiceTemplates/main.tf new file mode 100644 index 0000000..4d7e333 --- /dev/null +++ b/example/dataCdnServiceTemplates/main.tf @@ -0,0 +1 @@ +data "byteplus_cdn_service_templates" "foo" {} \ No newline at end of file diff --git a/go.mod b/go.mod index fc77f8f..97a19e6 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/hcl/v2 v2.0.0 github.com/hashicorp/terraform-plugin-sdk v1.7.0 + github.com/mitchellh/copystructure v1.0.0 github.com/stretchr/testify v1.8.2 golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 diff --git a/website/byteplus.erb b/website/byteplus.erb index 0a3c8c1..e3e913f 100644 --- a/website/byteplus.erb +++ b/website/byteplus.erb @@ -88,6 +88,84 @@ +