diff --git a/README.md b/README.md index 08b785b..3bc7ab7 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,10 @@ scenario. The reason behind every resources and data sources are stated as below This resource is designed to create auto scaling rules for AliCloud E-MapReduce cluster as the provider's resource [*alicloud_emrv2_cluster*](https://registry.terraform.io/providers/aliyun/alicloud/latest/docs/resources/emrv2_cluster) does not provide the option to create auto scaling rules for nodes. (Note: Only task nodes are eligible for auto scaling) +- **st-alicloud_ess_clb_default_server_group_attachment** + + This resource is designed to attach an auto scaling group (ESS) with a list of load balancers (CLB) default server group. + ### Data Sources - **st-alicloud_ddoscoo_domain_resources** diff --git a/alicloud/provider.go b/alicloud/provider.go index bbb4a6f..7dc4702 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -23,6 +23,7 @@ import ( alicloudEmrClient "github.com/alibabacloud-go/emr-20210320/client" alicloudRamClient "github.com/alibabacloud-go/ram-20150501/v2/client" alicloudSlbClient "github.com/alibabacloud-go/slb-20140515/v4/client" + alicloudEssClient "github.com/alibabacloud-go/ess-20220222/v2/client" "github.com/alibabacloud-go/tea/tea" ) @@ -39,6 +40,7 @@ type alicloudClients struct { adbClient *alicloudAdbClient.Client emrClient *alicloudEmrClient.Client csClient *alicloudCsClient.Client + essClient *alicloudEssClient.Client } // Ensure the implementation satisfies the expected interfaces @@ -341,6 +343,21 @@ func (p *alicloudProvider) Configure(ctx context.Context, req provider.Configure return } + // AliCloud ESS Client + essClientConfig := clientCredentialsConfig + essClientConfig.Endpoint = tea.String("ess.aliyuncs.com") + essClient, err := alicloudEssClient.NewClient(essClientConfig) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create AliCloud ESS API Client", + "An unexpected error occurred when creating the AliCloud ESS API client. "+ + "If the error is not clear, please contact the provider developers.\n\n"+ + "AliCloud ESS Client Error: "+err.Error(), + ) + return + } + // AliCloud clients wrapper alicloudClients := alicloudClients{ baseClient: baseClient, @@ -353,6 +370,7 @@ func (p *alicloudProvider) Configure(ctx context.Context, req provider.Configure adbClient: adbClient, emrClient: emrClient, csClient: csClient, + essClient: essClient, } resp.DataSourceData = alicloudClients @@ -383,5 +401,6 @@ func (p *alicloudProvider) Resources(_ context.Context) []func() resource.Resour NewAliadbResourceGroupBindResource, NewEmrMetricAutoScalingRulesResource, NewDdosCooWebAIProtectConfigResource, + NewEssClbDefaultServerGroupAttachmentResource, } } diff --git a/alicloud/resource_ess_clb_default_server_group_attachment.go b/alicloud/resource_ess_clb_default_server_group_attachment.go new file mode 100644 index 0000000..9585452 --- /dev/null +++ b/alicloud/resource_ess_clb_default_server_group_attachment.go @@ -0,0 +1,398 @@ +package alicloud + +import ( + "context" + "fmt" + "time" + + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/cenkalti/backoff/v4" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + alicloudEssClient "github.com/alibabacloud-go/ess-20220222/v2/client" +) + +var ( + _ resource.Resource = &essClbDefaultServerGroupAttachmentResource{} + _ resource.ResourceWithConfigure = &essClbDefaultServerGroupAttachmentResource{} +) + +func NewEssClbDefaultServerGroupAttachmentResource() resource.Resource { + return &essClbDefaultServerGroupAttachmentResource{} +} + +type essClbDefaultServerGroupAttachmentResource struct { + client *alicloudEssClient.Client +} + +type essClbDefaultServerGroupAttachmentModel struct { + ScalingGroupId types.String `tfsdk:"scaling_group_id"` + LoadBalancerIds types.List `tfsdk:"load_balancer_ids"` +} + +// Metadata returns the ESS CLB Default Server Group Attachment resource name. +func (r *essClbDefaultServerGroupAttachmentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_ess_clb_default_server_group_attachment" +} + +// Schema defines the schema for the ESS CLB Default Server Group Attachment resource. +func (r *essClbDefaultServerGroupAttachmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Attach an auto scaling group (ESS) with a list of load balancers (CLB) default server group.", + Attributes: map[string]schema.Attribute{ + "scaling_group_id": schema.StringAttribute{ + Description: "Scaling Group ID.", + Required: true, + }, + "load_balancer_ids": schema.ListAttribute{ + Description: "List of load balancer IDs.", + ElementType: types.StringType, + Required: true, + }, + }, + } +} + +// Configure adds the provider configured client to the resource. +func (r *essClbDefaultServerGroupAttachmentResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.client = req.ProviderData.(alicloudClients).essClient +} + +// Attach scaling group with load balancers' default server group. +func (r *essClbDefaultServerGroupAttachmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var plan *essClbDefaultServerGroupAttachmentModel + getStateDiags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(getStateDiags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.attachLoadBalancers(plan) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to attach scaling group with load balancers' default server group.", + err.Error(), + ) + return + } + + // Set state items + state := &essClbDefaultServerGroupAttachmentModel{ + ScalingGroupId: plan.ScalingGroupId, + LoadBalancerIds: plan.LoadBalancerIds, + } + + // Set state to fully populated data + setStateDiags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(setStateDiags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read the attached load balancers in the scaling group. +func (r *essClbDefaultServerGroupAttachmentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var state *essClbDefaultServerGroupAttachmentModel + getStateDiags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(getStateDiags...) + if resp.Diagnostics.HasError() { + return + } + + loadBalancerIds, scalingGroupId, err := r.getLoadBalancersFromScalingGroup(state) + if err != nil { + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to get attached load balancers from scaling group.", + err.Error(), + ) + return + } + } + + state = &essClbDefaultServerGroupAttachmentModel{ + ScalingGroupId: types.StringValue(scalingGroupId), + LoadBalancerIds: types.ListValueMust(types.StringType, loadBalancerIds), + } + + // Set state to fully populated data + setStateDiags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(setStateDiags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Attach or Detach scaling group with load balancers' default server group. +func (r *essClbDefaultServerGroupAttachmentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Retrieve values from plan + var plan *essClbDefaultServerGroupAttachmentModel + getPlanDiags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(getPlanDiags...) + if resp.Diagnostics.HasError() { + return + } + + // Get current state + var state *essClbDefaultServerGroupAttachmentModel + getStateDiags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(getStateDiags...) + if resp.Diagnostics.HasError() { + return + } + + loadBalancerIds, scalingGroupId, err := r.getLoadBalancersFromScalingGroup(state) + if err != nil { + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to get load balancers from scaling group.", + err.Error(), + ) + return + } + } + + if plan.ScalingGroupId == types.StringValue(scalingGroupId) { + stateLbs := make(map[string]struct{}) + planLbs := make(map[string]struct{}) + + for _, lb := range loadBalancerIds { + stateLbs[trimStringQuotes(lb.String())] = struct{}{} + } + for _, lb := range plan.LoadBalancerIds.Elements() { + planLbs[trimStringQuotes(lb.String())] = struct{}{} + } + + // Detach load balancer when load balancer from State does not exist in Plan. + var detachLbs []attr.Value + for _, lb := range loadBalancerIds { + if _, exists := planLbs[trimStringQuotes(lb.String())]; !exists { + detachLbs = append(detachLbs, types.StringValue(trimStringQuotes(lb.String()))) + } + } + if len(detachLbs) > 0 { + state.LoadBalancerIds = types.ListValueMust(types.StringType, detachLbs) + err = r.detachLoadBalancers(state) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to detach load balancers with scaling group.", + err.Error(), + ) + return + } + } + + // Attach load balancer when load balancer from Plan does not exist in State. + var attachLbs []attr.Value + for _, lb := range plan.LoadBalancerIds.Elements() { + if _, exists := stateLbs[trimStringQuotes(lb.String())]; !exists { + attachLbs = append(attachLbs, types.StringValue(trimStringQuotes(lb.String()))) + } + } + if len(attachLbs) > 0 { + state.LoadBalancerIds = types.ListValueMust(types.StringType, attachLbs) + err = r.attachLoadBalancers(plan) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to attach scaling group with load balancers' default server group.", + err.Error(), + ) + return + } + } + } else { + // attach a new scaling group with load balancers' default server group + err = r.attachLoadBalancers(plan) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to attach scaling group with load balancers' default server group.", + err.Error(), + ) + return + } + + // detach an old scaling group with load balancers' default server group + err = r.detachLoadBalancers(state) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to detach scaling group with load balancers' default server group.", + err.Error(), + ) + return + } + } + + // Set state items + state = &essClbDefaultServerGroupAttachmentModel{ + ScalingGroupId: plan.ScalingGroupId, + LoadBalancerIds: plan.LoadBalancerIds, + } + + // Set state to fully populated data + setStateDiags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(setStateDiags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Detach scaling group with load balancers' default server group. +func (r *essClbDefaultServerGroupAttachmentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Retrieve values from state + var state *essClbDefaultServerGroupAttachmentModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.detachLoadBalancers(state) + if err != nil { + resp.Diagnostics.AddError( + "[API ERROR] Failed to detach scaling group with load balancers' default server group.", + err.Error(), + ) + return + } +} + +// Function to read the attached load balancers in a scaling group. +func (r *essClbDefaultServerGroupAttachmentResource) getLoadBalancersFromScalingGroup(model *essClbDefaultServerGroupAttachmentModel) ([]attr.Value, string, error) { + var describeScalingGroupsResponse *alicloudEssClient.DescribeScalingGroupsResponse + var err error + var loadBalancers []attr.Value + var scalingGroupId string + + // Retry backoff function + describeScalingGroups := func() error { + runtime := &util.RuntimeOptions{} + + describeScalingGroupsRequest := &alicloudEssClient.DescribeScalingGroupsRequest{ + RegionId: r.client.RegionId, + ScalingGroupIds: []*string{tea.String(model.ScalingGroupId.ValueString())}, + } + + describeScalingGroupsResponse, err = r.client.DescribeScalingGroupsWithOptions(describeScalingGroupsRequest, runtime) + if err != nil { + if _t, ok := err.(*tea.SDKError); ok { + if isAbleToRetry(*_t.Code) { + return err + } else { + return backoff.Permanent(err) + } + } else { + return err + } + } + + return nil + } + + // Retry backoff + reconnectBackoff := backoff.NewExponentialBackOff() + reconnectBackoff.MaxElapsedTime = 30 * time.Second + err = backoff.Retry(describeScalingGroups, reconnectBackoff) + if err != nil { + return loadBalancers, scalingGroupId, err + } + + for _, scalingGroup := range describeScalingGroupsResponse.Body.ScalingGroups { + for _, loadBalancer := range scalingGroup.LoadBalancerIds { + loadBalancers = append(loadBalancers, types.StringValue(*loadBalancer)) + } + scalingGroupId = *scalingGroup.ScalingGroupId + } + return loadBalancers, scalingGroupId, nil +} + +// Function to attach scaling group with load balancers' default server group. +func (r *essClbDefaultServerGroupAttachmentResource) attachLoadBalancers(model *essClbDefaultServerGroupAttachmentModel) error { + attachLoadBalancers := func() error { + runtime := &util.RuntimeOptions{} + var loadBalancersIds []*string + + for _, id := range model.LoadBalancerIds.Elements() { + fmt.Print(id) + loadBalancersIds = append(loadBalancersIds, tea.String(trimStringQuotes(id.String()))) + } + + attachLoadBalancersRequest := &alicloudEssClient.AttachLoadBalancersRequest{ + ScalingGroupId: tea.String(model.ScalingGroupId.ValueString()), + LoadBalancers: loadBalancersIds, + ForceAttach: tea.Bool(true), + } + + _, _err := r.client.AttachLoadBalancersWithOptions(attachLoadBalancersRequest, runtime) + if _err != nil { + if _t, ok := _err.(*tea.SDKError); ok { + if isAbleToRetry(*_t.Code) { + return _err + } else { + return backoff.Permanent(_err) + } + } else { + return _err + } + } + return nil + } + + // Retry backoff + reconnectBackoff := backoff.NewExponentialBackOff() + reconnectBackoff.MaxElapsedTime = 30 * time.Second + err := backoff.Retry(attachLoadBalancers, reconnectBackoff) + if err != nil { + return err + } + return nil +} + +// Function to detach scaling group with load balancers' default server group. +func (r *essClbDefaultServerGroupAttachmentResource) detachLoadBalancers(model *essClbDefaultServerGroupAttachmentModel) error { + detachLoadBalancers := func() error { + runtime := &util.RuntimeOptions{} + var loadBalancersIds []*string + + for _, id := range model.LoadBalancerIds.Elements() { + loadBalancersIds = append(loadBalancersIds, tea.String(trimStringQuotes(id.String()))) + } + + detachLoadBalancersRequest := &alicloudEssClient.DetachLoadBalancersRequest{ + ScalingGroupId: tea.String(model.ScalingGroupId.ValueString()), + LoadBalancers: loadBalancersIds, + ForceDetach: tea.Bool(true), + } + + _, _err := r.client.DetachLoadBalancersWithOptions(detachLoadBalancersRequest, runtime) + if _err != nil { + if _t, ok := _err.(*tea.SDKError); ok { + if isAbleToRetry(*_t.Code) { + return _err + } else { + return backoff.Permanent(_err) + } + } else { + return _err + } + } + return nil + } + + // Retry backoff + reconnectBackoff := backoff.NewExponentialBackOff() + reconnectBackoff.MaxElapsedTime = 30 * time.Second + err := backoff.Retry(detachLoadBalancers, reconnectBackoff) + if err != nil { + return err + } + return nil +} diff --git a/docs/resources/ess_clb_default_server_group_attachment.md b/docs/resources/ess_clb_default_server_group_attachment.md new file mode 100644 index 0000000..28a8600 --- /dev/null +++ b/docs/resources/ess_clb_default_server_group_attachment.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "st-alicloud_ess_clb_default_server_group_attachment Resource - st-alicloud" +subcategory: "" +description: |- + Attach an auto scaling group (ESS) with a list of load balancers (CLB) default server group. +--- + +# st-alicloud_ess_clb_default_server_group_attachment (Resource) + +Attach an auto scaling group (ESS) with a list of load balancers (CLB) default server group. + +## Example Usage + +```terraform +resource "st-alicloud_ess_clb_default_server_group_attachment" "example" { + scaling_group_id = "asg-xxxxxxxxxxxxxxxxxxxx" + load_balancer_ids = ["lb-xxxxxxxxxxxxxxxxxxxxx"] +} +``` + + +## Schema + +### Required + +- `load_balancer_ids` (List of String) List of load balancer IDs. +- `scaling_group_id` (String) Scaling Group ID. diff --git a/examples/resources/st-alicloud_ess_clb_default_server_group_attachment/resource.tf b/examples/resources/st-alicloud_ess_clb_default_server_group_attachment/resource.tf new file mode 100644 index 0000000..81e4803 --- /dev/null +++ b/examples/resources/st-alicloud_ess_clb_default_server_group_attachment/resource.tf @@ -0,0 +1,4 @@ +resource "st-alicloud_ess_clb_default_server_group_attachment" "example" { + scaling_group_id = "asg-xxxxxxxxxxxxxxxxxxxx" + load_balancer_ids = ["lb-xxxxxxxxxxxxxxxxxxxxx"] +} diff --git a/go.mod b/go.mod index 5212648..74b0937 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/alibabacloud-go/adb-20190315/v2 v2.1.2 github.com/alibabacloud-go/bssopenapi-20171214/v3 v3.0.2 + github.com/alibabacloud-go/ess-20220222/v2 v2.0.10 github.com/alibabacloud-go/slb-20140515/v4 v4.0.1 github.com/cenkalti/backoff v2.2.1+incompatible github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index d8e0872..1b9948a 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/alibabacloud-go/emr-20210320 v1.1.0/go.mod h1:KNj6VyWDaCYI4Da6Ejf7GCb github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/ess-20220222/v2 v2.0.10 h1:+dDXKOwvPhtuKY+DGgkbRsjKdNUWvaxp06IrplKK9U8= +github.com/alibabacloud-go/ess-20220222/v2 v2.0.10/go.mod h1:XuSnQD4PBLrfegI8BIu9Un4yfUqX7QUoL8SresjZwkE= github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=