diff --git a/docs/resources/apig_group.md b/docs/resources/apig_group.md index f7057156bd..74e134af69 100644 --- a/docs/resources/apig_group.md +++ b/docs/resources/apig_group.md @@ -66,6 +66,9 @@ The following arguments are supported: * `domain_access_enabled` - (Optional, Bool) Specifies whether to use the debugging domain name to access the APIs within the group. The default value is `true`. +* `force_destroy` - (Optional, Bool) Specifies whether to delete all sub-resources (for API) when deleting this group. + Defaults to **false**. + The `environment` block supports: diff --git a/huaweicloud/services/apig/resource_huaweicloud_apig_api.go b/huaweicloud/services/apig/resource_huaweicloud_apig_api.go index 1c0c6dc7e7..d3a1556e1b 100644 --- a/huaweicloud/services/apig/resource_huaweicloud_apig_api.go +++ b/huaweicloud/services/apig/resource_huaweicloud_apig_api.go @@ -1761,6 +1761,24 @@ func resourceApiUpdate(ctx context.Context, d *schema.ResourceData, meta interfa return resourceApiRead(ctx, d, meta) } +func deleteApi(client *golangsdk.ServiceClient, instanceId, apiId string) error { + httpUrl := "v2/{project_id}/apigw/instances/{instance_id}/apis/{api_id}" + unpublishPath := client.Endpoint + httpUrl + unpublishPath = strings.ReplaceAll(unpublishPath, "{project_id}", client.ProjectID) + unpublishPath = strings.ReplaceAll(unpublishPath, "{instance_id}", instanceId) + unpublishPath = strings.ReplaceAll(unpublishPath, "{api_id}", apiId) + + opt := golangsdk.RequestOpts{ + KeepResponseBody: true, + MoreHeaders: map[string]string{ + "Content-Type": "application/json", + }, + } + + _, err := client.Request("DELETE", unpublishPath, &opt) + return err +} + func resourceApiDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { cfg := meta.(*config.Config) client, err := cfg.ApigV2Client(cfg.GetRegion(d)) @@ -1772,7 +1790,7 @@ func resourceApiDelete(_ context.Context, d *schema.ResourceData, meta interface instanceId = d.Get("instance_id").(string) apiId = d.Id() ) - if err = apis.Delete(client, instanceId, apiId).ExtractErr(); err != nil { + if err = deleteApi(client, instanceId, apiId); err != nil { return diag.Errorf("unable to delete the API (%s): %s", apiId, err) } diff --git a/huaweicloud/services/apig/resource_huaweicloud_apig_group.go b/huaweicloud/services/apig/resource_huaweicloud_apig_group.go index 07a7d0767b..32e1164e5b 100644 --- a/huaweicloud/services/apig/resource_huaweicloud_apig_group.go +++ b/huaweicloud/services/apig/resource_huaweicloud_apig_group.go @@ -29,6 +29,8 @@ import ( // @API APIG POST /v2/{project_id}/apigw/instances/{instance_id}/api-groups/{group_id}/domains // @API APIG DELETE /v2/{project_id}/apigw/instances/{instance_id}/api-groups/{group_id}/domains/{domain_id} // @API APIG PUT /v2/{project_id}/apigw/instances/{instance_id}/api-groups/{group_id}/sl-domain-access-settings +// @API APIG GET /v2/{project_id}/apigw/instances/{instance_id}/apis +// @API APIG POST /v2/{project_id}/apigw/instances/{instance_id}/apis/publish func ResourceApigGroupV2() *schema.Resource { return &schema.Resource{ CreateContext: resourceGroupCreate, @@ -138,6 +140,12 @@ func ResourceApigGroupV2() *schema.Resource { Default: true, Description: "Specifies whether to use the debugging domain name to access the APIs within the group.", }, + "force_destroy": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Whether to delete all sub-resources (for API) from this group.", + }, "created_at": { Type: schema.TypeString, Computed: true, @@ -345,6 +353,7 @@ func resourceGroupRead(_ context.Context, d *schema.ResourceData, meta interface d.Set("description", resp.Description), d.Set("url_domains", flattenUrlDomain(resp.UrlDomians)), d.Set("domain_access_enabled", resp.SlDomainAccessEnabled), + d.Set("force_destroy", d.Get("force_destroy")), d.Set("created_at", resp.RegistraionTime), d.Set("updated_at", resp.UpdateTime), // Deprecated attributes @@ -512,13 +521,123 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter return resourceGroupRead(ctx, d, meta) } +func queryApisInGroup(client *golangsdk.ServiceClient, instanceId, groupId string) ([]interface{}, error) { + var ( + httpUrl = "v2/{project_id}/apigw/instances/{instance_id}/apis?group_id={group_id}&limit=500" + offset = 0 + result = make([]interface{}, 0) + ) + listPath := client.Endpoint + httpUrl + listPath = strings.ReplaceAll(listPath, "{project_id}", client.ProjectID) + listPath = strings.ReplaceAll(listPath, "{instance_id}", instanceId) + listPath = strings.ReplaceAll(listPath, "{group_id}", groupId) + + opt := golangsdk.RequestOpts{ + KeepResponseBody: true, + MoreHeaders: map[string]string{ + "Content-Type": "application/json", + }, + } + + for { + listPathWithOffsset := fmt.Sprintf("%s&offset=%d", listPath, offset) + requestResp, err := client.Request("GET", listPathWithOffsset, &opt) + if err != nil { + return nil, fmt.Errorf("error querying API list: %s", err) + } + respBody, err := utils.FlattenResponse(requestResp) + if err != nil { + return nil, err + } + apiRecords := utils.PathSearch("apis", respBody, make([]interface{}, 0)).([]interface{}) + if len(apiRecords) < 1 { + break + } + result = append(result, apiRecords...) + offset += len(apiRecords) + } + return result, nil +} + +func parsePublishInfo(apiRecords []interface{}) map[string][]interface{} { + result := make(map[string][]interface{}) + for _, apiRecord := range apiRecords { + apiId := utils.PathSearch("id", apiRecord, "").(string) + if apiId == "" { + log.Printf("[ERROR] The API ID field is missing.") + continue + } + publishedEnvs := utils.PathSearch("run_env_id", apiRecord, "").(string) + for _, env := range strings.Split(strings.TrimSpace(publishedEnvs), "|") { + if _, ok := result[env]; ok { + result[env] = append(result[env], apiId) + continue + } + result[env] = []interface{}{apiId} + } + } + return result +} + +func batchUnpublishApis(client *golangsdk.ServiceClient, instanceId, envId string, apiIds []interface{}) error { + httpUrl := "v2/{project_id}/apigw/instances/{instance_id}/apis/publish?action=offline" + unpublishPath := client.Endpoint + httpUrl + unpublishPath = strings.ReplaceAll(unpublishPath, "{project_id}", client.ProjectID) + unpublishPath = strings.ReplaceAll(unpublishPath, "{instance_id}", instanceId) + + opt := golangsdk.RequestOpts{ + KeepResponseBody: true, + MoreHeaders: map[string]string{ + "Content-Type": "application/json", + }, + JSONBody: map[string]interface{}{ + "apis": apiIds, + "env_id": envId, + }, + } + + _, err := client.Request("POST", unpublishPath, &opt) + return err +} + +func forceUnpublishApis(client *golangsdk.ServiceClient, instanceId, groupId string) error { + apiRecords, err := queryApisInGroup(client, instanceId, groupId) + if err != nil { + return err + } + // Unpublish first if they have published APIs. + for envId, unpublishApiIds := range parsePublishInfo(apiRecords) { + if err = batchUnpublishApis(client, instanceId, envId, unpublishApiIds); err != nil { + log.Printf("[ERROR] Error unpublishing API from environment (%s): %s", envId, err) + } + } + // Remove all APIs. + for _, apiId := range utils.PathSearch("[*].id", apiRecords, make([]interface{}, 0)).([]interface{}) { + if err = deleteApi(client, instanceId, apiId.(string)); err != nil { + log.Printf("[ERROR] Error deleting API (%s): %s", apiId, err) + } + } + return nil +} + func resourceGroupDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - cfg := meta.(*config.Config) - client, err := cfg.ApigV2Client(cfg.GetRegion(d)) + var ( + cfg = meta.(*config.Config) + region = cfg.GetRegion(d) + groupId = d.Id() + instanceId = d.Get("instance_id").(string) + ) + client, err := cfg.ApigV2Client(region) if err != nil { return diag.Errorf("error creating APIG v2 client: %s", err) } - instanceId := d.Get("instance_id").(string) + + if d.Get("force_destroy").(bool) { + if err = forceUnpublishApis(client, instanceId, groupId); err != nil { + log.Printf("[ERROR] Unable to clean the sub-resources (for API) under group (%s): %s", groupId, err) + } + } + err = apigroups.Delete(client, instanceId, d.Id()).ExtractErr() if err != nil { return diag.Errorf("error deleting group from the instance (%s): %s", instanceId, err)