144 lines
4.2 KiB
Go
144 lines
4.2 KiB
Go
package main
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"flag"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
|
||
|
|
"yovision/sense/internal/device"
|
||
|
|
"yovision/sense/internal/store"
|
||
|
|
)
|
||
|
|
|
||
|
|
type manifest struct {
|
||
|
|
Site manifestSite `json:"site"`
|
||
|
|
Devices []manifestDevice `json:"devices"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type manifestSite struct {
|
||
|
|
TenantID string `json:"tenant_id"`
|
||
|
|
ID string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
MaxVideoChannels int `json:"max_video_channels"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type manifestDevice struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
TenantID string `json:"tenant_id"`
|
||
|
|
SiteID string `json:"site_id"`
|
||
|
|
SerialNumber string `json:"serial_number"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Capabilities []string `json:"capabilities"`
|
||
|
|
EndpointRef string `json:"endpoint_ref"`
|
||
|
|
CredentialRef string `json:"credential_ref"`
|
||
|
|
PathName string `json:"path_name"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
if err := run(os.Args[1:], os.Stdout); err != nil {
|
||
|
|
_, _ = fmt.Fprintln(os.Stderr, err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func run(args []string, output io.Writer) error {
|
||
|
|
if len(args) == 0 {
|
||
|
|
return errors.New("usage: sense-lab <seed|status>")
|
||
|
|
}
|
||
|
|
switch args[0] {
|
||
|
|
case "seed":
|
||
|
|
return seed(args[1:], output)
|
||
|
|
case "status":
|
||
|
|
return status(args[1:], output)
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("unknown command %q", args[0])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func seed(args []string, output io.Writer) error {
|
||
|
|
flags := flag.NewFlagSet("seed", flag.ContinueOnError)
|
||
|
|
flags.SetOutput(io.Discard)
|
||
|
|
dsn := flags.String("db", "", "SQLite DSN")
|
||
|
|
manifestPath := flags.String("manifest", "", "manifest JSON path")
|
||
|
|
if err := flags.Parse(args); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if *dsn == "" || *manifestPath == "" {
|
||
|
|
return errors.New("seed requires -db and -manifest")
|
||
|
|
}
|
||
|
|
file, err := os.Open(*manifestPath)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("open manifest: %w", err)
|
||
|
|
}
|
||
|
|
defer file.Close()
|
||
|
|
var value manifest
|
||
|
|
decoder := json.NewDecoder(io.LimitReader(file, 1<<20))
|
||
|
|
decoder.DisallowUnknownFields()
|
||
|
|
if err := decoder.Decode(&value); err != nil {
|
||
|
|
return fmt.Errorf("decode manifest: %w", err)
|
||
|
|
}
|
||
|
|
repository, err := store.OpenSQLite(context.Background(), *dsn)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer repository.Close()
|
||
|
|
ctx := context.Background()
|
||
|
|
if err := repository.EnsureSite(ctx, device.Site{
|
||
|
|
TenantID: value.Site.TenantID, ID: value.Site.ID, Name: value.Site.Name,
|
||
|
|
MaxVideoChannels: value.Site.MaxVideoChannels,
|
||
|
|
}); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for _, input := range value.Devices {
|
||
|
|
capabilities := make([]device.Capability, 0, len(input.Capabilities))
|
||
|
|
for _, capability := range input.Capabilities {
|
||
|
|
capabilities = append(capabilities, device.Capability(capability))
|
||
|
|
}
|
||
|
|
if err := repository.CreateDevice(ctx, device.Device{
|
||
|
|
ID: input.ID, TenantID: input.TenantID, SiteID: input.SiteID,
|
||
|
|
SerialNumber: input.SerialNumber, Name: input.Name, Modality: device.ModalityVideo,
|
||
|
|
Capabilities: capabilities, DesiredState: device.DesiredEnabled, ActualState: device.ActualPending,
|
||
|
|
EndpointRef: input.EndpointRef, CredentialRef: input.CredentialRef, PathName: input.PathName,
|
||
|
|
}); err != nil {
|
||
|
|
return fmt.Errorf("create device %s: %w", input.ID, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return json.NewEncoder(output).Encode(map[string]int{"seeded": len(value.Devices)})
|
||
|
|
}
|
||
|
|
|
||
|
|
func status(args []string, output io.Writer) error {
|
||
|
|
flags := flag.NewFlagSet("status", flag.ContinueOnError)
|
||
|
|
flags.SetOutput(io.Discard)
|
||
|
|
dsn := flags.String("db", "", "SQLite DSN")
|
||
|
|
expect := flags.Int("expect", -1, "expected device count")
|
||
|
|
requireConverged := flags.Bool("require-converged", false, "fail when unconverged is non-zero")
|
||
|
|
if err := flags.Parse(args); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if *dsn == "" {
|
||
|
|
return errors.New("status requires -db")
|
||
|
|
}
|
||
|
|
repository, err := store.OpenSQLite(context.Background(), *dsn)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer repository.Close()
|
||
|
|
snapshot, err := repository.ConvergenceSnapshot(context.Background())
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := json.NewEncoder(output).Encode(snapshot); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if *expect >= 0 && snapshot.Total != *expect {
|
||
|
|
return fmt.Errorf("expected %d devices, got %d", *expect, snapshot.Total)
|
||
|
|
}
|
||
|
|
if *requireConverged && snapshot.Unconverged != 0 {
|
||
|
|
return fmt.Errorf("unconverged devices: %d", snapshot.Unconverged)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|