ec/ecresource/projectresource/elasticsearch.go (286 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package projectresource import ( "context" "fmt" "net/http" "time" "github.com/elastic/terraform-provider-ec/ec/internal/gen/serverless" "github.com/elastic/terraform-provider-ec/ec/internal/gen/serverless/resource_elasticsearch_project" "github.com/elastic/terraform-provider-ec/ec/internal/util" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) func NewElasticsearchProjectResource() *Resource[resource_elasticsearch_project.ElasticsearchProjectModel] { return &Resource[resource_elasticsearch_project.ElasticsearchProjectModel]{ modelHandler: elasticsearchModelReader{}, api: elasticsearchApi{ sleeper: realSleeper{}, }, name: "elasticsearch", } } type elasticsearchModelReader struct{} func (es elasticsearchModelReader) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resource_elasticsearch_project.ElasticsearchProjectResourceSchema(ctx) } func (es elasticsearchModelReader) ReadFrom(ctx context.Context, getter modelGetter) (*resource_elasticsearch_project.ElasticsearchProjectModel, diag.Diagnostics) { return readFrom[resource_elasticsearch_project.ElasticsearchProjectModel](ctx, getter) } func (es elasticsearchModelReader) GetID(model resource_elasticsearch_project.ElasticsearchProjectModel) string { return model.Id.ValueString() } func (es elasticsearchModelReader) Modify(plan resource_elasticsearch_project.ElasticsearchProjectModel, state resource_elasticsearch_project.ElasticsearchProjectModel, cfg resource_elasticsearch_project.ElasticsearchProjectModel) resource_elasticsearch_project.ElasticsearchProjectModel { plan.Credentials = useStateForUnknown(plan.Credentials, state.Credentials) plan.Endpoints = useStateForUnknown(plan.Endpoints, state.Endpoints) plan.Metadata = useStateForUnknown(plan.Metadata, state.Metadata) nameHasChanged := !plan.Name.Equal(state.Name) aliasIsConfigured := util.IsKnown(cfg.Alias) aliasHasChanged := !plan.Alias.Equal(state.Alias) cloudIDIsUnknown := nameHasChanged || aliasHasChanged aliasIsUnknown := nameHasChanged && !aliasIsConfigured endpointsAreUnknown := aliasHasChanged || (!aliasIsConfigured && nameHasChanged) if cloudIDIsUnknown { plan.CloudId = basetypes.NewStringUnknown() } if aliasIsUnknown { plan.Alias = basetypes.NewStringUnknown() } if endpointsAreUnknown { plan.Endpoints = resource_elasticsearch_project.NewEndpointsValueUnknown() } return plan } type sleeper interface { Sleep(time.Duration) } type realSleeper struct{} func (r realSleeper) Sleep(d time.Duration) { time.Sleep(d) } type elasticsearchApi struct { client serverless.ClientWithResponsesInterface sleeper sleeper } func (es elasticsearchApi) Ready() bool { return es.client != nil } func (es elasticsearchApi) WithClient(client serverless.ClientWithResponsesInterface) api[resource_elasticsearch_project.ElasticsearchProjectModel] { es.client = client return es } func (es elasticsearchApi) Create(ctx context.Context, model resource_elasticsearch_project.ElasticsearchProjectModel) (resource_elasticsearch_project.ElasticsearchProjectModel, diag.Diagnostics) { createBody := serverless.CreateElasticsearchProjectRequest{ Name: model.Name.ValueString(), RegionId: model.RegionId.ValueString(), } if model.Alias.ValueString() != "" { createBody.Alias = model.Alias.ValueStringPointer() } if model.OptimizedFor.ValueString() != "" { createBody.OptimizedFor = (*serverless.ElasticsearchOptimizedFor)(model.OptimizedFor.ValueStringPointer()) } if util.IsKnown(model.SearchLake) { createBody.SearchLake = &serverless.ElasticsearchSearchLake{} if util.IsKnown(model.SearchLake.BoostWindow) { boostWindow := int(model.SearchLake.BoostWindow.ValueInt64()) createBody.SearchLake.BoostWindow = &boostWindow } if util.IsKnown(model.SearchLake.SearchPower) { searchPower := int(model.SearchLake.SearchPower.ValueInt64()) createBody.SearchLake.SearchPower = &searchPower } } resp, err := es.client.CreateElasticsearchProjectWithResponse(ctx, createBody) if err != nil { return model, diag.Diagnostics{ diag.NewErrorDiagnostic(err.Error(), err.Error()), } } if resp.JSON201 == nil { return model, diag.Diagnostics{ diag.NewErrorDiagnostic( "Failed to create elasticsearch_project", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } model.Id = types.StringValue(resp.JSON201.Id) creds, diags := resource_elasticsearch_project.NewCredentialsValue( model.Credentials.AttributeTypes(ctx), map[string]attr.Value{ "username": types.StringValue(resp.JSON201.Credentials.Username), "password": types.StringValue(resp.JSON201.Credentials.Password), }, ) model.Credentials = creds return model, diags } func (es elasticsearchApi) Patch(ctx context.Context, model resource_elasticsearch_project.ElasticsearchProjectModel) diag.Diagnostics { updateBody := serverless.PatchElasticsearchProjectRequest{ Name: model.Name.ValueStringPointer(), } if model.Alias.ValueString() != "" { updateBody.Alias = model.Alias.ValueStringPointer() } if util.IsKnown(model.SearchLake) { updateBody.SearchLake = &serverless.OptionalElasticsearchSearchLake{} if util.IsKnown(model.SearchLake.BoostWindow) { boostWindow := int(model.SearchLake.BoostWindow.ValueInt64()) updateBody.SearchLake.BoostWindow = &boostWindow } if util.IsKnown(model.SearchLake.SearchPower) { searchPower := int(model.SearchLake.SearchPower.ValueInt64()) updateBody.SearchLake.SearchPower = &searchPower } } resp, err := es.client.PatchElasticsearchProjectWithResponse(ctx, model.Id.ValueString(), nil, updateBody) if err != nil { return diag.Diagnostics{ diag.NewErrorDiagnostic(err.Error(), err.Error()), } } if resp.JSON200 == nil { return diag.Diagnostics{ diag.NewErrorDiagnostic( "Failed to update elasticsearch_project", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } return nil } func (es elasticsearchApi) EnsureInitialised(ctx context.Context, model resource_elasticsearch_project.ElasticsearchProjectModel) diag.Diagnostics { id := model.Id.ValueString() for { resp, err := es.client.GetElasticsearchProjectStatusWithResponse(ctx, id) if err != nil { return diag.Diagnostics{ diag.NewErrorDiagnostic(err.Error(), err.Error()), } } if resp.JSON200 == nil { return diag.Diagnostics{ diag.NewErrorDiagnostic( "Failed to get elasticsearch_project status", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } if resp.JSON200.Phase == serverless.Initialized { return nil } es.sleeper.Sleep(200 * time.Millisecond) } } func (es elasticsearchApi) Read(ctx context.Context, id string, model resource_elasticsearch_project.ElasticsearchProjectModel) (bool, resource_elasticsearch_project.ElasticsearchProjectModel, diag.Diagnostics) { resp, err := es.client.GetElasticsearchProjectWithResponse(ctx, id) if err != nil { return false, model, diag.Diagnostics{ diag.NewErrorDiagnostic(err.Error(), err.Error()), } } if resp.HTTPResponse != nil && resp.HTTPResponse.StatusCode == http.StatusNotFound { return false, model, nil } if resp.JSON200 == nil { return false, model, diag.Diagnostics{ diag.NewErrorDiagnostic( "Failed to read elasticsearch_project", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } model.Id = basetypes.NewStringValue(id) model.Alias = basetypes.NewStringValue(reformatAlias(resp.JSON200.Alias, id)) model.CloudId = basetypes.NewStringValue(resp.JSON200.CloudId) endpoints, diags := resource_elasticsearch_project.NewEndpointsValue( model.Endpoints.AttributeTypes(ctx), map[string]attr.Value{ "elasticsearch": basetypes.NewStringValue(resp.JSON200.Endpoints.Elasticsearch), "kibana": basetypes.NewStringValue(resp.JSON200.Endpoints.Kibana), }, ) if diags.HasError() { return false, model, diags } model.Endpoints = endpoints metadataValues := map[string]attr.Value{ "created_at": basetypes.NewStringValue(resp.JSON200.Metadata.CreatedAt.String()), "created_by": basetypes.NewStringValue(resp.JSON200.Metadata.CreatedBy), "organization_id": basetypes.NewStringValue(resp.JSON200.Metadata.OrganizationId), "suspended_at": basetypes.NewStringNull(), "suspended_reason": basetypes.NewStringPointerValue(resp.JSON200.Metadata.SuspendedReason), } if resp.JSON200.Metadata.SuspendedAt != nil { metadataValues["suspended_at"] = basetypes.NewStringValue(resp.JSON200.Metadata.SuspendedAt.String()) } metadata, diags := resource_elasticsearch_project.NewMetadataValue( model.Metadata.AttributeTypes(ctx), metadataValues, ) if diags.HasError() { return false, model, diags } model.Metadata = metadata model.Name = basetypes.NewStringValue(resp.JSON200.Name) model.OptimizedFor = basetypes.NewStringValue(string(resp.JSON200.OptimizedFor)) model.RegionId = basetypes.NewStringValue(resp.JSON200.RegionId) model.Type = basetypes.NewStringValue(string(resp.JSON200.Type)) searchLakeValues := map[string]attr.Value{ "boost_window": basetypes.NewInt64Null(), "search_power": basetypes.NewInt64Null(), } if resp.JSON200.SearchLake != nil { if resp.JSON200.SearchLake.BoostWindow != nil { searchLakeValues["boost_window"] = basetypes.NewInt64Value(int64(*resp.JSON200.SearchLake.BoostWindow)) } if resp.JSON200.SearchLake.SearchPower != nil { searchLakeValues["search_power"] = basetypes.NewInt64Value(int64(*resp.JSON200.SearchLake.SearchPower)) } } searchLake, diags := resource_elasticsearch_project.NewSearchLakeValue( model.SearchLake.AttributeTypes(ctx), searchLakeValues, ) if diags.HasError() { return false, model, nil } model.SearchLake = searchLake return true, model, nil } func (es elasticsearchApi) Delete(ctx context.Context, model resource_elasticsearch_project.ElasticsearchProjectModel) diag.Diagnostics { resp, err := es.client.DeleteElasticsearchProjectWithResponse(ctx, model.Id.ValueString(), nil) if err != nil { return diag.Diagnostics{ diag.NewErrorDiagnostic("Failed to delete elasticsearch_project", err.Error()), } } statusCode := resp.StatusCode() if statusCode != 200 && statusCode != 404 { return diag.Diagnostics{ diag.NewErrorDiagnostic( "Request to delete elasticsearch_project failed", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } return nil }