ec/ecresource/projectresource/resource.go (94 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"
"github.com/elastic/terraform-provider-ec/ec/internal"
"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/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &Resource[resource_elasticsearch_project.ElasticsearchProjectModel]{}
var _ resource.ResourceWithConfigure = &Resource[resource_elasticsearch_project.ElasticsearchProjectModel]{}
var _ resource.ResourceWithModifyPlan = &Resource[resource_elasticsearch_project.ElasticsearchProjectModel]{}
type Resource[T any] struct {
modelHandler modelHandler[T]
api api[T]
name string
}
type modelGetter interface {
Get(ctx context.Context, target interface{}) diag.Diagnostics
}
// mockgen doesn't support the recursive generic used within api.WithClient
// Check if https://github.com/uber-go/mock/issues/175 has been fixed
// //go:generate go run go.uber.org/mock/mockgen -source=resource.go -destination mocks.gen.go -package projectresource .
type modelHandler[T any] interface {
Schema(context.Context, resource.SchemaRequest, *resource.SchemaResponse)
ReadFrom(context.Context, modelGetter) (*T, diag.Diagnostics)
GetID(T) string
Modify(T, T, T) T
}
type api[TModel any] interface {
Create(context.Context, TModel) (TModel, diag.Diagnostics)
Patch(context.Context, TModel) diag.Diagnostics
EnsureInitialised(context.Context, TModel) diag.Diagnostics
Read(context.Context, string, TModel) (bool, TModel, diag.Diagnostics)
Delete(context.Context, TModel) diag.Diagnostics
WithClient(serverless.ClientWithResponsesInterface) api[TModel]
Ready() bool
}
func resourceReady[T any](r *Resource[T], dg *diag.Diagnostics) bool {
if !r.api.Ready() {
dg.AddError(
"Unconfigured API Client",
"Expected configured API client. Please report this issue to the provider developers.",
)
return false
}
return true
}
func readFrom[T any](ctx context.Context, getter modelGetter) (*T, diag.Diagnostics) {
var model *T
diags := getter.Get(ctx, &model)
return model, diags
}
func (r *Resource[T]) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
clients, diags := internal.ConvertProviderData(request.ProviderData)
response.Diagnostics.Append(diags...)
r.api = r.api.WithClient(clients.Serverless)
}
func (r *Resource[T]) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = fmt.Sprintf("%s_%s_project", request.ProviderTypeName, r.name)
}
func (r *Resource[T]) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
r.modelHandler.Schema(ctx, req, resp)
}
func (r Resource[T]) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
cfgModel, diags := r.modelHandler.ReadFrom(ctx, req.Config)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
planModel, diags := r.modelHandler.ReadFrom(ctx, req.Plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
stateModel, diags := r.modelHandler.ReadFrom(ctx, req.State)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// If state is nil then we're creating, if planModel is nil then we're deleting.
// There's no need for further modification in either case
if stateModel == nil || planModel == nil {
return
}
modifiedModel := r.modelHandler.Modify(*planModel, *stateModel, *cfgModel)
resp.Diagnostics.Append(resp.Plan.Set(ctx, modifiedModel)...)
}
func useStateForUnknown[T basetypes.ObjectValuable](planValue T, stateValue T) T {
if stateValue.IsNull() || stateValue.IsUnknown() {
return planValue
}
if planValue.IsUnknown() {
return stateValue
}
return planValue
}