Files
fission-console/terraform/provider/internal/resources/iot_device_resource.go
T

231 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package resources
import (
"context"
"fmt"
"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/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"terraform-provider-fission/internal/client"
)
var _ resource.Resource = &IoTDeviceResource{}
// IoTDeviceResource управляет CRD IoTDevice (iot.kube5s.ru/v1alpha1).
type IoTDeviceResource struct {
client *client.Client
}
type iotDeviceResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Namespace types.String `tfsdk:"namespace"`
DeviceID types.String `tfsdk:"device_id"`
Enabled types.Bool `tfsdk:"enabled"`
Metadata types.Map `tfsdk:"metadata"`
Phase types.String `tfsdk:"phase"`
MQTTUsername types.String `tfsdk:"mqtt_username"`
SecretName types.String `tfsdk:"secret_name"`
TopicPrefix types.String `tfsdk:"topic_prefix"`
}
func NewIoTDeviceResource() resource.Resource {
return &IoTDeviceResource{}
}
func (r *IoTDeviceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_iot_device"
}
func (r *IoTDeviceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "IoT-устройство, зарегистрированное в платформе sless (CRD iot.kube5s.ru/v1alpha1/IoTDevice).",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Идентификатор ресурса (namespace/name).",
},
"name": schema.StringAttribute{
Required: true,
Description: "Имя IoTDevice.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"namespace": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString("sless"),
Description: "Namespace IoTDevice. По умолчанию sless.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"device_id": schema.StringAttribute{
Required: true,
Description: "Уникальный ID устройства внутри namespace (строчные буквы, цифры, дефис).",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
Default: booldefault.StaticBool(true),
Description: "Активно ли устройство.",
},
"metadata": schema.MapAttribute{
Optional: true,
Computed: true,
ElementType: types.StringType,
Default: mapdefault.StaticValue(types.MapValueMust(types.StringType, map[string]attr.Value{})),
Description: "Произвольные метаданные устройства (модель, локация и т.д.).",
},
// Computed (заполняет iot-operator)
"phase": schema.StringAttribute{
Computed: true,
Description: "Текущая фаза: Active, Disabled, Pending, Error.",
},
"mqtt_username": schema.StringAttribute{
Computed: true,
Description: "MQTT username, выданный iot-operator.",
},
"secret_name": schema.StringAttribute{
Computed: true,
Description: "Имя K8s Secret с MQTT credentials.",
},
"topic_prefix": schema.StringAttribute{
Computed: true,
Description: "MQTT topic prefix для публикации.",
},
},
}
}
func (r *IoTDeviceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
c, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError("Неверный тип провайдера", fmt.Sprintf("ожидался *client.Client, получен %T", req.ProviderData))
return
}
r.client = c
}
func (r *IoTDeviceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan iotDeviceResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
ns := plan.Namespace.ValueString()
meta := map[string]string{}
if !plan.Metadata.IsNull() && !plan.Metadata.IsUnknown() {
elems := plan.Metadata.Elements()
for k, v := range elems {
if sv, ok := v.(types.String); ok {
meta[k] = sv.ValueString()
}
}
}
info, err := r.client.CreateIoTDevice(ctx, plan.Name.ValueString(), ns, plan.DeviceID.ValueString(), meta)
if err != nil {
resp.Diagnostics.AddError("Ошибка создания IoT device", err.Error())
return
}
plan.ID = types.StringValue(ns + "/" + plan.Name.ValueString())
plan.Phase = types.StringValue(info.Phase)
plan.MQTTUsername = types.StringValue(info.MQTTUsername)
plan.SecretName = types.StringValue(info.SecretName)
plan.TopicPrefix = types.StringValue(info.TopicPrefix)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *IoTDeviceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state iotDeviceResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
info, err := r.client.GetIoTDevice(ctx, state.Namespace.ValueString(), state.Name.ValueString())
if err != nil {
if client.IsNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("Ошибка чтения IoT device", err.Error())
return
}
state.Phase = types.StringValue(info.Phase)
state.MQTTUsername = types.StringValue(info.MQTTUsername)
state.SecretName = types.StringValue(info.SecretName)
state.TopicPrefix = types.StringValue(info.TopicPrefix)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *IoTDeviceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// name/namespace/device_id — RequiresReplace, значит Update только для enabled/metadata.
// Для простоты: пересоздаём объект.
var plan iotDeviceResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
ns := plan.Namespace.ValueString()
name := plan.Name.ValueString()
if err := r.client.DeleteIoTDevice(ctx, ns, name); err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления IoT device при обновлении", err.Error())
return
}
meta := map[string]string{}
if !plan.Metadata.IsNull() && !plan.Metadata.IsUnknown() {
elems := plan.Metadata.Elements()
for k, v := range elems {
if sv, ok := v.(types.String); ok {
meta[k] = sv.ValueString()
}
}
}
info, err := r.client.CreateIoTDevice(ctx, name, ns, plan.DeviceID.ValueString(), meta)
if err != nil {
resp.Diagnostics.AddError("Ошибка создания IoT device при обновлении", err.Error())
return
}
plan.Phase = types.StringValue(info.Phase)
plan.MQTTUsername = types.StringValue(info.MQTTUsername)
plan.SecretName = types.StringValue(info.SecretName)
plan.TopicPrefix = types.StringValue(info.TopicPrefix)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *IoTDeviceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state iotDeviceResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.DeleteIoTDevice(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("Ошибка удаления IoT device", err.Error())
}
}