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()) } }