ec/ecresource/projectresource/observability.go (232 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_observability_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 NewObservabilityProjectResource() *Resource[resource_observability_project.ObservabilityProjectModel] { return &Resource[resource_observability_project.ObservabilityProjectModel]{ modelHandler: observabilityModelReader{}, api: observabilityApi{sleeper: realSleeper{}}, name: "observability", } } type observabilityModelReader struct{} func (obs observabilityModelReader) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resource_observability_project.ObservabilityProjectResourceSchema(ctx) } func (obs observabilityModelReader) ReadFrom(ctx context.Context, getter modelGetter) (*resource_observability_project.ObservabilityProjectModel, diag.Diagnostics) { return readFrom[resource_observability_project.ObservabilityProjectModel](ctx, getter) } func (obs observabilityModelReader) GetID(model resource_observability_project.ObservabilityProjectModel) string { return model.Id.ValueString() } func (obs observabilityModelReader) Modify(plan resource_observability_project.ObservabilityProjectModel, state resource_observability_project.ObservabilityProjectModel, cfg resource_observability_project.ObservabilityProjectModel) resource_observability_project.ObservabilityProjectModel { 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_observability_project.NewEndpointsValueUnknown() } return plan } type observabilityApi struct { client serverless.ClientWithResponsesInterface sleeper sleeper } func (obs observabilityApi) Ready() bool { return obs.client != nil } func (obs observabilityApi) WithClient(client serverless.ClientWithResponsesInterface) api[resource_observability_project.ObservabilityProjectModel] { obs.client = client return obs } func (obs observabilityApi) Create(ctx context.Context, model resource_observability_project.ObservabilityProjectModel) (resource_observability_project.ObservabilityProjectModel, diag.Diagnostics) { createBody := serverless.CreateObservabilityProjectRequest{ Name: model.Name.ValueString(), RegionId: model.RegionId.ValueString(), } if model.Alias.ValueString() != "" { createBody.Alias = model.Alias.ValueStringPointer() } resp, err := obs.client.CreateObservabilityProjectWithResponse(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 observability_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_observability_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 (obs observabilityApi) Patch(ctx context.Context, model resource_observability_project.ObservabilityProjectModel) diag.Diagnostics { updateBody := serverless.PatchObservabilityProjectRequest{ Name: model.Name.ValueStringPointer(), } if model.Alias.ValueString() != "" { updateBody.Alias = model.Alias.ValueStringPointer() } resp, err := obs.client.PatchObservabilityProjectWithResponse(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 observability_project", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } return nil } func (obs observabilityApi) EnsureInitialised(ctx context.Context, model resource_observability_project.ObservabilityProjectModel) diag.Diagnostics { id := model.Id.ValueString() for { resp, err := obs.client.GetObservabilityProjectStatusWithResponse(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 observability_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 } obs.sleeper.Sleep(200 * time.Millisecond) } } func (obs observabilityApi) Read(ctx context.Context, id string, model resource_observability_project.ObservabilityProjectModel) (bool, resource_observability_project.ObservabilityProjectModel, diag.Diagnostics) { resp, err := obs.client.GetObservabilityProjectWithResponse(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 observability_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_observability_project.NewEndpointsValue( model.Endpoints.AttributeTypes(ctx), map[string]attr.Value{ "elasticsearch": basetypes.NewStringValue(resp.JSON200.Endpoints.Elasticsearch), "kibana": basetypes.NewStringValue(resp.JSON200.Endpoints.Kibana), "apm": basetypes.NewStringValue(resp.JSON200.Endpoints.Apm), }, ) 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_observability_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.RegionId = basetypes.NewStringValue(resp.JSON200.RegionId) model.Type = basetypes.NewStringValue(string(resp.JSON200.Type)) return true, model, nil } func (obs observabilityApi) Delete(ctx context.Context, model resource_observability_project.ObservabilityProjectModel) diag.Diagnostics { resp, err := obs.client.DeleteObservabilityProjectWithResponse(ctx, model.Id.ValueString(), nil) if err != nil { return diag.Diagnostics{ diag.NewErrorDiagnostic("Failed to delete observability_project", err.Error()), } } statusCode := resp.StatusCode() if statusCode != 200 && statusCode != 404 { return diag.Diagnostics{ diag.NewErrorDiagnostic( "Request to delete observability_project failed", fmt.Sprintf("The API request failed with: %d %s\n%s", resp.StatusCode(), resp.Status(), resp.Body), ), } } return nil }