feat(iot): Этап 1 — CRD IoTDevice + контроллер credentials
- iot/api/v1alpha1/device_types.go — CRD IoTDevice - iot/api/v1alpha1/groupversion_info.go — API group iot.kube5s.ru/v1alpha1 - iot/api/v1alpha1/zz_generated.deepcopy.go — deepcopy (controller-gen) - iot/config/crd/bases/iot.kube5s.ru_iotdevices.yaml — CRD манифест - iot/controllers/iotdevice_controller.go — Reconcile: Secret с MQTT credentials - main.go — регистрация IoT схемы и IoTDeviceReconciler go build ./... — ошибок нет
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// Создано: 2026-04-04
|
||||
// Описание CRD IoTDevice — регистрация IoT-устройства в платформе sless.
|
||||
// Пользователь создаёт IoTDevice, контроллер автоматически генерирует MQTT-credentials
|
||||
// и сохраняет их в k8s Secret в том же namespace.
|
||||
// При удалении IoTDevice — Secret удаляется каскадно через OwnerReference.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IoTDeviceSpec — желаемое состояние IoT-устройства.
|
||||
type IoTDeviceSpec struct {
|
||||
// DeviceID — уникальный идентификатор устройства внутри namespace.
|
||||
// Используется как часть MQTT username и имени Secret.
|
||||
// Разрешены только строчные буквы, цифры и дефис — для совместимости с k8s именами.
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9-]*[a-z0-9]$`
|
||||
// +kubebuilder:validation:MaxLength=48
|
||||
DeviceID string `json:"deviceId"`
|
||||
|
||||
// Metadata — произвольные метаданные устройства (модель, локация и т.д.).
|
||||
// Хранятся только в CRD, не влияют на логику контроллера.
|
||||
// +optional
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
|
||||
// Enabled — активно ли устройство (может подключаться к MQTT).
|
||||
// Если false — контроллер устанавливает phase=Disabled, EMQX auth отклоняет подключение.
|
||||
// Secret с credentials НЕ удаляется — при re-enable пароль остаётся прежним.
|
||||
// +kubebuilder:default=true
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// IoTDevicePhase — текущая фаза жизненного цикла устройства.
|
||||
type IoTDevicePhase string
|
||||
|
||||
const (
|
||||
// IoTDevicePhaseActive — устройство активно, credentials выданы, может подключаться к MQTT.
|
||||
IoTDevicePhaseActive IoTDevicePhase = "Active"
|
||||
// IoTDevicePhaseDisabled — устройство отключено (spec.enabled=false). EMQX отклоняет подключения.
|
||||
IoTDevicePhaseDisabled IoTDevicePhase = "Disabled"
|
||||
// IoTDevicePhasePending — устройство создано, контроллер ещё не успел сгенерировать credentials.
|
||||
IoTDevicePhasePending IoTDevicePhase = "Pending"
|
||||
// IoTDevicePhaseError — ошибка при создании credentials (см. status.message).
|
||||
IoTDevicePhaseError IoTDevicePhase = "Error"
|
||||
)
|
||||
|
||||
// IoTDeviceStatus — наблюдаемое состояние IoT-устройства (заполняет контроллер).
|
||||
type IoTDeviceStatus struct {
|
||||
// Phase — текущее состояние: Active, Disabled, Pending, Error.
|
||||
Phase IoTDevicePhase `json:"phase,omitempty"`
|
||||
|
||||
// MQTTUsername — имя пользователя для подключения к MQTT-брокеру.
|
||||
// Формат: {namespace}_{deviceId} — глобально уникален в рамках EMQX.
|
||||
MQTTUsername string `json:"mqttUsername,omitempty"`
|
||||
|
||||
// SecretName — имя k8s Secret в том же namespace, содержащего mqtt-username и mqtt-password.
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
|
||||
// TopicPrefix — MQTT topic prefix, на который разрешена публикация.
|
||||
// Формат: {namespace}/ — устройство не может публиковать в чужие namespace.
|
||||
TopicPrefix string `json:"topicPrefix,omitempty"`
|
||||
|
||||
// LastConnected — время последнего MQTT-подключения устройства.
|
||||
// Заполняется MQTT auth-сервисом при каждом успешном CONNECT.
|
||||
// +optional
|
||||
LastConnected *metav1.Time `json:"lastConnected,omitempty"`
|
||||
|
||||
// Message — человекочитаемое сообщение о текущем статусе или ошибке.
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="DeviceID",type=string,JSONPath=`.spec.deviceId`
|
||||
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||
// +kubebuilder:printcolumn:name="Enabled",type=boolean,JSONPath=`.spec.enabled`
|
||||
// +kubebuilder:printcolumn:name="MQTTUser",type=string,JSONPath=`.status.mqttUsername`
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||
|
||||
// IoTDevice — ресурс для регистрации IoT-устройства в платформе.
|
||||
// Контроллер автоматически создаёт k8s Secret с MQTT-credentials.
|
||||
type IoTDevice struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec IoTDeviceSpec `json:"spec,omitempty"`
|
||||
Status IoTDeviceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// IoTDeviceList — список IoTDevice объектов.
|
||||
type IoTDeviceList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []IoTDevice `json:"items"`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Создано: 2026-04-04
|
||||
// groupversion_info.go — регистрация API group iot.kube5s.ru/v1alpha1.
|
||||
// ВАЖНО: группа намеренно отдельная от sless.kube5s.ru — при выносе IoT в
|
||||
// отдельную репу CRD не будет конфликтовать с sless CRD.
|
||||
|
||||
// Package v1alpha1 содержит API Schema definitions для группы iot.kube5s.ru.
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=iot.kube5s.ru
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion — API group/version для IoT ресурсов.
|
||||
// Отдельный от sless.kube5s.ru — для изоляции и будущего разделения репозиториев.
|
||||
GroupVersion = schema.GroupVersion{Group: "iot.kube5s.ru", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder регистрирует Go-типы в GroupVersionKind scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme добавляет типы этой API группы в scheme manager'а.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&IoTDevice{}, &IoTDeviceList{})
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IoTDevice) DeepCopyInto(out *IoTDevice) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IoTDevice.
|
||||
func (in *IoTDevice) DeepCopy() *IoTDevice {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IoTDevice)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *IoTDevice) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IoTDeviceList) DeepCopyInto(out *IoTDeviceList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]IoTDevice, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IoTDeviceList.
|
||||
func (in *IoTDeviceList) DeepCopy() *IoTDeviceList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IoTDeviceList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *IoTDeviceList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IoTDeviceSpec) DeepCopyInto(out *IoTDeviceSpec) {
|
||||
*out = *in
|
||||
if in.Metadata != nil {
|
||||
in, out := &in.Metadata, &out.Metadata
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IoTDeviceSpec.
|
||||
func (in *IoTDeviceSpec) DeepCopy() *IoTDeviceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IoTDeviceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IoTDeviceStatus) DeepCopyInto(out *IoTDeviceStatus) {
|
||||
*out = *in
|
||||
if in.LastConnected != nil {
|
||||
in, out := &in.LastConnected, &out.LastConnected
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IoTDeviceStatus.
|
||||
func (in *IoTDeviceStatus) DeepCopy() *IoTDeviceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IoTDeviceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user