- sqs-consumer: new Go binary v1.2 with tokenManager (auto-login /auth/login, 5min cache, retry on 401) - console: add MQ/KW triggers UI (mqtriggers.go, kwtriggers.go, mq.js) - console: update python-env image to v1.1 (boto3+psycopg2+requests) - weather-demo: fix consumer/main.py (Flask Request.get_json instead of dict access) - weather-demo: fix fetcher/main.py (boto3 SQS publish, 5 cities) - weather-demo: update main.tf (python-env v1.1, deploy_type=literal) - python-env: add psycopg2-binary to Dockerfile (v1.1) - terraform provider: client auth fix
520 lines
18 KiB
Go
520 lines
18 KiB
Go
package client
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
appsv1 "k8s.io/api/apps/v1"
|
||
corev1 "k8s.io/api/core/v1"
|
||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||
k8sresource "k8s.io/apimachinery/pkg/api/resource"
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||
"k8s.io/client-go/dynamic"
|
||
"k8s.io/client-go/kubernetes"
|
||
"k8s.io/client-go/rest"
|
||
"k8s.io/client-go/tools/clientcmd"
|
||
)
|
||
|
||
var environmentGVR = schema.GroupVersionResource{
|
||
Group: "fission.io",
|
||
Version: "v1",
|
||
Resource: "environments",
|
||
}
|
||
|
||
var packageGVR = schema.GroupVersionResource{
|
||
Group: "fission.io",
|
||
Version: "v1",
|
||
Resource: "packages",
|
||
}
|
||
|
||
var functionGVR = schema.GroupVersionResource{
|
||
Group: "fission.io",
|
||
Version: "v1",
|
||
Resource: "functions",
|
||
}
|
||
|
||
var httpTriggerGVR = schema.GroupVersionResource{
|
||
Group: "fission.io",
|
||
Version: "v1",
|
||
Resource: "httptriggers",
|
||
}
|
||
|
||
var timeTriggerGVR = schema.GroupVersionResource{
|
||
Group: "fission.io",
|
||
Version: "v1",
|
||
Resource: "timetriggers",
|
||
}
|
||
|
||
var iotDeviceGVR = schema.GroupVersionResource{
|
||
Group: "iot.kube5s.ru",
|
||
Version: "v1alpha1",
|
||
Resource: "iotdevices",
|
||
}
|
||
|
||
const (
|
||
sqsConsumerImage = "naeel/sqs-consumer:v1.0"
|
||
sqsDefaultEndpoint = "http://shared-sqs.shared-sqs.svc.cluster.local:4100"
|
||
fissionRouterBase = "http://router.fission.svc.cluster.local"
|
||
mqManagedByLabel = "app.kubernetes.io/managed-by"
|
||
mqManagedByVal = "fission-terraform"
|
||
mqComponentLabel = "component"
|
||
mqComponentVal = "mq-trigger"
|
||
)
|
||
|
||
// Client хранит клиентов Kubernetes API для работы с CRD Fission.
|
||
type Client struct {
|
||
DynClient dynamic.Interface
|
||
K8sClient kubernetes.Interface
|
||
Namespace string
|
||
}
|
||
|
||
// New создает Kubernetes clients из kubeconfig/context.
|
||
func New(kubeconfigPath, kubeContext, namespace string) (*Client, error) {
|
||
cfg, err := buildConfig(kubeconfigPath, kubeContext)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
dynClient, err := dynamic.NewForConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create dynamic client: %w", err)
|
||
}
|
||
|
||
k8sClient, err := kubernetes.NewForConfig(cfg)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create kubernetes client: %w", err)
|
||
}
|
||
|
||
return &Client{
|
||
DynClient: dynClient,
|
||
K8sClient: k8sClient,
|
||
Namespace: namespace,
|
||
}, nil
|
||
}
|
||
|
||
func buildConfig(kubeconfigPath, kubeContext string) (*rest.Config, error) {
|
||
loadingRules := &clientcmd.ClientConfigLoadingRules{}
|
||
if kubeconfigPath != "" {
|
||
loadingRules.ExplicitPath = kubeconfigPath
|
||
}
|
||
|
||
overrides := &clientcmd.ConfigOverrides{}
|
||
if kubeContext != "" {
|
||
overrides.CurrentContext = kubeContext
|
||
}
|
||
|
||
clientCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
|
||
cfg, err := clientCfg.ClientConfig()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("build kube config: %w", err)
|
||
}
|
||
|
||
return cfg, nil
|
||
}
|
||
|
||
// CreateEnvironment создает объект Environment в заданном namespace.
|
||
func (c *Client) CreateEnvironment(ctx context.Context, environment *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
createdEnvironment, err := c.DynClient.Resource(environmentGVR).Namespace(environment.GetNamespace()).Create(ctx, environment, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create environment %q: %w", environment.GetName(), err)
|
||
}
|
||
|
||
return createdEnvironment, nil
|
||
}
|
||
|
||
// GetEnvironment получает объект Environment по имени.
|
||
func (c *Client) GetEnvironment(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||
environment, err := c.DynClient.Resource(environmentGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get environment %q: %w", name, err)
|
||
}
|
||
|
||
return environment, nil
|
||
}
|
||
|
||
// UpdateEnvironment обновляет существующий объект Environment.
|
||
func (c *Client) UpdateEnvironment(ctx context.Context, environment *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
updatedEnvironment, err := c.DynClient.Resource(environmentGVR).Namespace(environment.GetNamespace()).Update(ctx, environment, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update environment %q: %w", environment.GetName(), err)
|
||
}
|
||
|
||
return updatedEnvironment, nil
|
||
}
|
||
|
||
// DeleteEnvironment удаляет объект Environment по имени.
|
||
func (c *Client) DeleteEnvironment(ctx context.Context, namespace, name string) error {
|
||
err := c.DynClient.Resource(environmentGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||
if err != nil {
|
||
return fmt.Errorf("delete environment %q: %w", name, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// IsNotFound возвращает true для ошибок Kubernetes типа NotFound.
|
||
func IsNotFound(err error) bool {
|
||
return apierrors.IsNotFound(err)
|
||
}
|
||
|
||
// CreatePackage создает объект Package в заданном namespace.
|
||
func (c *Client) CreatePackage(ctx context.Context, pkg *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
createdPackage, err := c.DynClient.Resource(packageGVR).Namespace(pkg.GetNamespace()).Create(ctx, pkg, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create package %q: %w", pkg.GetName(), err)
|
||
}
|
||
|
||
return createdPackage, nil
|
||
}
|
||
|
||
// GetPackage получает объект Package по имени.
|
||
func (c *Client) GetPackage(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||
pkg, err := c.DynClient.Resource(packageGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get package %q: %w", name, err)
|
||
}
|
||
|
||
return pkg, nil
|
||
}
|
||
|
||
// UpdatePackage обновляет существующий объект Package.
|
||
func (c *Client) UpdatePackage(ctx context.Context, pkg *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
updatedPackage, err := c.DynClient.Resource(packageGVR).Namespace(pkg.GetNamespace()).Update(ctx, pkg, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update package %q: %w", pkg.GetName(), err)
|
||
}
|
||
|
||
return updatedPackage, nil
|
||
}
|
||
|
||
// DeletePackage удаляет объект Package по имени.
|
||
func (c *Client) DeletePackage(ctx context.Context, namespace, name string) error {
|
||
err := c.DynClient.Resource(packageGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||
if err != nil {
|
||
return fmt.Errorf("delete package %q: %w", name, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateFunction создает объект Function в заданном namespace.
|
||
func (c *Client) CreateFunction(ctx context.Context, fn *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
createdFunction, err := c.DynClient.Resource(functionGVR).Namespace(fn.GetNamespace()).Create(ctx, fn, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create function %q: %w", fn.GetName(), err)
|
||
}
|
||
|
||
return createdFunction, nil
|
||
}
|
||
|
||
// GetFunction получает объект Function по имени.
|
||
func (c *Client) GetFunction(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||
fn, err := c.DynClient.Resource(functionGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get function %q: %w", name, err)
|
||
}
|
||
|
||
return fn, nil
|
||
}
|
||
|
||
// UpdateFunction обновляет существующий объект Function.
|
||
func (c *Client) UpdateFunction(ctx context.Context, fn *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
updatedFunction, err := c.DynClient.Resource(functionGVR).Namespace(fn.GetNamespace()).Update(ctx, fn, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update function %q: %w", fn.GetName(), err)
|
||
}
|
||
|
||
return updatedFunction, nil
|
||
}
|
||
|
||
// DeleteFunction удаляет объект Function по имени.
|
||
func (c *Client) DeleteFunction(ctx context.Context, namespace, name string) error {
|
||
err := c.DynClient.Resource(functionGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||
if err != nil {
|
||
return fmt.Errorf("delete function %q: %w", name, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateHTTPTrigger создает объект HTTPTrigger в заданном namespace.
|
||
func (c *Client) CreateHTTPTrigger(ctx context.Context, trigger *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
createdTrigger, err := c.DynClient.Resource(httpTriggerGVR).Namespace(trigger.GetNamespace()).Create(ctx, trigger, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create http trigger %q: %w", trigger.GetName(), err)
|
||
}
|
||
|
||
return createdTrigger, nil
|
||
}
|
||
|
||
// GetHTTPTrigger получает объект HTTPTrigger по имени.
|
||
func (c *Client) GetHTTPTrigger(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||
trigger, err := c.DynClient.Resource(httpTriggerGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get http trigger %q: %w", name, err)
|
||
}
|
||
|
||
return trigger, nil
|
||
}
|
||
|
||
// UpdateHTTPTrigger обновляет существующий объект HTTPTrigger.
|
||
func (c *Client) UpdateHTTPTrigger(ctx context.Context, trigger *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
updatedTrigger, err := c.DynClient.Resource(httpTriggerGVR).Namespace(trigger.GetNamespace()).Update(ctx, trigger, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update http trigger %q: %w", trigger.GetName(), err)
|
||
}
|
||
|
||
return updatedTrigger, nil
|
||
}
|
||
|
||
// DeleteHTTPTrigger удаляет объект HTTPTrigger по имени.
|
||
func (c *Client) DeleteHTTPTrigger(ctx context.Context, namespace, name string) error {
|
||
err := c.DynClient.Resource(httpTriggerGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||
if err != nil {
|
||
return fmt.Errorf("delete http trigger %q: %w", name, err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ── MQ Trigger (K8s Deployment + Secret) ────────────────────────────
|
||
|
||
func mqSecretName(name string) string { return "sqs-mq-" + name }
|
||
func mqDeployName(name string) string { return "mq-" + name }
|
||
|
||
// MQTriggerInfo — данные MQ trigger, считанные из K8s.
|
||
type MQTriggerInfo struct {
|
||
Function string
|
||
Queue string
|
||
SQSEndpoint string
|
||
DeployUID string
|
||
}
|
||
|
||
// CreateMQTrigger создаёт Secret + Deployment для SQS consumer.
|
||
func (c *Client) CreateMQTrigger(ctx context.Context, name, ns, functionName, queue, sqsEndpoint, accessKey, secretKey string) (*MQTriggerInfo, error) {
|
||
if sqsEndpoint == "" {
|
||
sqsEndpoint = sqsDefaultEndpoint
|
||
}
|
||
functionURL := fissionRouterBase + "/" + functionName
|
||
secretName := mqSecretName(name)
|
||
deployName := mqDeployName(name)
|
||
|
||
labels := map[string]string{
|
||
mqManagedByLabel: mqManagedByVal,
|
||
mqComponentLabel: mqComponentVal,
|
||
"mq-trigger-name": name,
|
||
}
|
||
|
||
secret := &corev1.Secret{
|
||
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: ns, Labels: labels},
|
||
StringData: map[string]string{
|
||
"SQS_ACCESS_KEY": accessKey,
|
||
"SQS_SECRET_KEY": secretKey,
|
||
"SQS_ENDPOINT": sqsEndpoint,
|
||
},
|
||
}
|
||
if _, err := c.K8sClient.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
|
||
return nil, fmt.Errorf("create mq secret %q: %w", secretName, err)
|
||
}
|
||
|
||
replicas := int32(1)
|
||
deploy := &appsv1.Deployment{
|
||
ObjectMeta: metav1.ObjectMeta{
|
||
Name: deployName,
|
||
Namespace: ns,
|
||
Labels: labels,
|
||
Annotations: map[string]string{
|
||
"fission-terraform/mq-trigger-name": name,
|
||
"fission-terraform/function": functionName,
|
||
"fission-terraform/queue": queue,
|
||
"fission-terraform/sqs-endpoint": sqsEndpoint,
|
||
},
|
||
},
|
||
Spec: appsv1.DeploymentSpec{
|
||
Replicas: &replicas,
|
||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"mq-trigger-name": name}},
|
||
Template: corev1.PodTemplateSpec{
|
||
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
|
||
"mq-trigger-name": name,
|
||
mqComponentLabel: mqComponentVal,
|
||
}},
|
||
Spec: corev1.PodSpec{
|
||
Containers: []corev1.Container{{
|
||
Name: "sqs-consumer",
|
||
Image: sqsConsumerImage,
|
||
ImagePullPolicy: corev1.PullAlways,
|
||
Env: []corev1.EnvVar{
|
||
{Name: "SQS_QUEUE_NAME", Value: queue},
|
||
{Name: "SQS_REGION", Value: "us-east-1"},
|
||
{Name: "FUNCTION_URL", Value: functionURL},
|
||
{Name: "POLL_INTERVAL", Value: "5"},
|
||
{Name: "MAX_MESSAGES", Value: "1"},
|
||
{Name: "MAX_RETRIES", Value: "3"},
|
||
},
|
||
EnvFrom: []corev1.EnvFromSource{{
|
||
SecretRef: &corev1.SecretEnvSource{
|
||
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
|
||
},
|
||
}},
|
||
Resources: corev1.ResourceRequirements{
|
||
Limits: corev1.ResourceList{
|
||
corev1.ResourceCPU: k8sresource.MustParse("50m"),
|
||
corev1.ResourceMemory: k8sresource.MustParse("32Mi"),
|
||
},
|
||
Requests: corev1.ResourceList{
|
||
corev1.ResourceCPU: k8sresource.MustParse("10m"),
|
||
corev1.ResourceMemory: k8sresource.MustParse("16Mi"),
|
||
},
|
||
},
|
||
}},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
created, err := c.K8sClient.AppsV1().Deployments(ns).Create(ctx, deploy, metav1.CreateOptions{})
|
||
if err != nil {
|
||
_ = c.K8sClient.CoreV1().Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})
|
||
return nil, fmt.Errorf("create mq deployment %q: %w", deployName, err)
|
||
}
|
||
|
||
return &MQTriggerInfo{
|
||
Function: functionName,
|
||
Queue: queue,
|
||
SQSEndpoint: sqsEndpoint,
|
||
DeployUID: string(created.UID),
|
||
}, nil
|
||
}
|
||
|
||
// GetMQTrigger возвращает информацию о MQ trigger по аннотациям Deployment.
|
||
func (c *Client) GetMQTrigger(ctx context.Context, ns, name string) (*MQTriggerInfo, error) {
|
||
deploy, err := c.K8sClient.AppsV1().Deployments(ns).Get(ctx, mqDeployName(name), metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get mq deployment %q: %w", name, err)
|
||
}
|
||
ann := deploy.Annotations
|
||
return &MQTriggerInfo{
|
||
Function: ann["fission-terraform/function"],
|
||
Queue: ann["fission-terraform/queue"],
|
||
SQSEndpoint: ann["fission-terraform/sqs-endpoint"],
|
||
DeployUID: string(deploy.UID),
|
||
}, nil
|
||
}
|
||
|
||
// DeleteMQTrigger удаляет Deployment + Secret MQ trigger.
|
||
func (c *Client) DeleteMQTrigger(ctx context.Context, ns, name string) error {
|
||
dErr := c.K8sClient.AppsV1().Deployments(ns).Delete(ctx, mqDeployName(name), metav1.DeleteOptions{})
|
||
sErr := c.K8sClient.CoreV1().Secrets(ns).Delete(ctx, mqSecretName(name), metav1.DeleteOptions{})
|
||
if dErr != nil && !apierrors.IsNotFound(dErr) {
|
||
return fmt.Errorf("delete mq deployment %q: %w", name, dErr)
|
||
}
|
||
if sErr != nil && !apierrors.IsNotFound(sErr) {
|
||
return fmt.Errorf("delete mq secret %q: %w", name, sErr)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ── TimeTrigger (Fission CRD) ────────────────────────────────────────
|
||
|
||
// CreateTimeTrigger создаёт объект TimeTrigger (CRON) в заданном namespace.
|
||
func (c *Client) CreateTimeTrigger(ctx context.Context, tt *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
created, err := c.DynClient.Resource(timeTriggerGVR).Namespace(tt.GetNamespace()).Create(ctx, tt, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create time trigger %q: %w", tt.GetName(), err)
|
||
}
|
||
return created, nil
|
||
}
|
||
|
||
// GetTimeTrigger возвращает объект TimeTrigger по имени.
|
||
func (c *Client) GetTimeTrigger(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||
tt, err := c.DynClient.Resource(timeTriggerGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get time trigger %q: %w", name, err)
|
||
}
|
||
return tt, nil
|
||
}
|
||
|
||
// UpdateTimeTrigger обновляет объект TimeTrigger.
|
||
func (c *Client) UpdateTimeTrigger(ctx context.Context, tt *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||
updated, err := c.DynClient.Resource(timeTriggerGVR).Namespace(tt.GetNamespace()).Update(ctx, tt, metav1.UpdateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("update time trigger %q: %w", tt.GetName(), err)
|
||
}
|
||
return updated, nil
|
||
}
|
||
|
||
// DeleteTimeTrigger удаляет объект TimeTrigger по имени.
|
||
func (c *Client) DeleteTimeTrigger(ctx context.Context, namespace, name string) error {
|
||
if err := c.DynClient.Resource(timeTriggerGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil {
|
||
return fmt.Errorf("delete time trigger %q: %w", name, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ── IoTDevice (CRD iot.kube5s.ru/v1alpha1) ──────────────────────────
|
||
|
||
// IoTDeviceInfo — данные IoTDevice из статуса CRD.
|
||
type IoTDeviceInfo struct {
|
||
Phase string
|
||
MQTTUsername string
|
||
SecretName string
|
||
TopicPrefix string
|
||
}
|
||
|
||
// CreateIoTDevice создаёт объект IoTDevice.
|
||
func (c *Client) CreateIoTDevice(ctx context.Context, name, namespace, deviceID string, metadata map[string]string) (*IoTDeviceInfo, error) {
|
||
obj := &unstructured.Unstructured{
|
||
Object: map[string]any{
|
||
"apiVersion": "iot.kube5s.ru/v1alpha1",
|
||
"kind": "IoTDevice",
|
||
"metadata": map[string]any{
|
||
"name": name,
|
||
"namespace": namespace,
|
||
},
|
||
"spec": map[string]any{
|
||
"deviceId": deviceID,
|
||
"enabled": true,
|
||
"metadata": metadata,
|
||
},
|
||
},
|
||
}
|
||
created, err := c.DynClient.Resource(iotDeviceGVR).Namespace(namespace).Create(ctx, obj, metav1.CreateOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create iot device %q: %w", name, err)
|
||
}
|
||
return extractIoTDeviceInfo(created), nil
|
||
}
|
||
|
||
// GetIoTDevice возвращает IoTDevice по имени.
|
||
func (c *Client) GetIoTDevice(ctx context.Context, namespace, name string) (*IoTDeviceInfo, error) {
|
||
obj, err := c.DynClient.Resource(iotDeviceGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get iot device %q: %w", name, err)
|
||
}
|
||
return extractIoTDeviceInfo(obj), nil
|
||
}
|
||
|
||
// DeleteIoTDevice удаляет IoTDevice по имени.
|
||
func (c *Client) DeleteIoTDevice(ctx context.Context, namespace, name string) error {
|
||
if err := c.DynClient.Resource(iotDeviceGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil {
|
||
return fmt.Errorf("delete iot device %q: %w", name, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func extractIoTDeviceInfo(obj *unstructured.Unstructured) *IoTDeviceInfo {
|
||
status, _, _ := unstructured.NestedMap(obj.Object, "status")
|
||
phase, _ := status["phase"].(string)
|
||
mqttUsername, _ := status["mqttUsername"].(string)
|
||
secretName, _ := status["secretName"].(string)
|
||
topicPrefix, _ := status["topicPrefix"].(string)
|
||
return &IoTDeviceInfo{
|
||
Phase: phase,
|
||
MQTTUsername: mqttUsername,
|
||
SecretName: secretName,
|
||
TopicPrefix: topicPrefix,
|
||
}
|
||
}
|