ec/ecresource/projectresource/security.go (249 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_security_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 NewSecurityProjectResource() *Resource[resource_security_project.SecurityProjectModel] {
return &Resource[resource_security_project.SecurityProjectModel]{
modelHandler: securityModelReader{},
api: securityApi{sleeper: realSleeper{}},
name: "security",
}
}
type securityModelReader struct{}
func (sec securityModelReader) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resource_security_project.SecurityProjectResourceSchema(ctx)
}
func (sec securityModelReader) ReadFrom(ctx context.Context, getter modelGetter) (*resource_security_project.SecurityProjectModel, diag.Diagnostics) {
return readFrom[resource_security_project.SecurityProjectModel](ctx, getter)
}
func (sec securityModelReader) GetID(model resource_security_project.SecurityProjectModel) string {
return model.Id.ValueString()
}
func (sec securityModelReader) Modify(plan resource_security_project.SecurityProjectModel, state resource_security_project.SecurityProjectModel, cfg resource_security_project.SecurityProjectModel) resource_security_project.SecurityProjectModel {
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_security_project.NewEndpointsValueUnknown()
}
return plan
}
type securityApi struct {
client serverless.ClientWithResponsesInterface
sleeper sleeper
}
func (sec securityApi) Ready() bool {
return sec.client != nil
}
func (sec securityApi) WithClient(client serverless.ClientWithResponsesInterface) api[resource_security_project.SecurityProjectModel] {
sec.client = client
return sec
}
func (sec securityApi) Create(ctx context.Context, model resource_security_project.SecurityProjectModel) (resource_security_project.SecurityProjectModel, diag.Diagnostics) {
createBody := serverless.CreateSecurityProjectRequest{
Name: model.Name.ValueString(),
RegionId: model.RegionId.ValueString(),
}
if model.Alias.ValueString() != "" {
createBody.Alias = model.Alias.ValueStringPointer()
}
if model.AdminFeaturesPackage.ValueString() != "" {
createBody.AdminFeaturesPackage = (*serverless.SecurityAdminFeaturesPackage)(model.AdminFeaturesPackage.ValueStringPointer())
}
if util.IsKnown(model.ProductTypes) {
var productTypes []resource_security_project.ProductTypesValue
diags := model.ProductTypes.ElementsAs(ctx, &productTypes, false)
if diags.HasError() {
return model, diags
}
createProductTypes := []serverless.SecurityProductType{}
for _, productType := range productTypes {
createProductTypes = append(createProductTypes, serverless.SecurityProductType{
ProductLine: serverless.SecurityProductLine(productType.ProductLine.ValueString()),
ProductTier: serverless.SecurityProductTier(productType.ProductTier.ValueString()),
})
}
createBody.ProductTypes = &createProductTypes
}
resp, err := sec.client.CreateSecurityProjectWithResponse(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 security_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_security_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 (sec securityApi) Patch(ctx context.Context, model resource_security_project.SecurityProjectModel) diag.Diagnostics {
updateBody := serverless.PatchSecurityProjectRequest{
Name: model.Name.ValueStringPointer(),
}
if model.Alias.ValueString() != "" {
updateBody.Alias = model.Alias.ValueStringPointer()
}
resp, err := sec.client.PatchSecurityProjectWithResponse(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 security_project",
fmt.Sprintf("The API request failed with: %d %s\n%s",
resp.StatusCode(),
resp.Status(),
resp.Body),
),
}
}
return nil
}
func (sec securityApi) EnsureInitialised(ctx context.Context, model resource_security_project.SecurityProjectModel) diag.Diagnostics {
id := model.Id.ValueString()
for {
resp, err := sec.client.GetSecurityProjectStatusWithResponse(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 security_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
}
sec.sleeper.Sleep(200 * time.Millisecond)
}
}
func (sec securityApi) Read(ctx context.Context, id string, model resource_security_project.SecurityProjectModel) (bool, resource_security_project.SecurityProjectModel, diag.Diagnostics) {
resp, err := sec.client.GetSecurityProjectWithResponse(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 security_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_security_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_security_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 (sec securityApi) Delete(ctx context.Context, model resource_security_project.SecurityProjectModel) diag.Diagnostics {
resp, err := sec.client.DeleteSecurityProjectWithResponse(ctx, model.Id.ValueString(), nil)
if err != nil {
return diag.Diagnostics{
diag.NewErrorDiagnostic("Failed to delete security_project", err.Error()),
}
}
statusCode := resp.StatusCode()
if statusCode != 200 && statusCode != 404 {
return diag.Diagnostics{
diag.NewErrorDiagnostic(
"Request to delete security_project failed",
fmt.Sprintf("The API request failed with: %d %s\n%s",
resp.StatusCode(),
resp.Status(),
resp.Body),
),
}
}
return nil
}