From d209efaddde6392efe751a4f9df2f8001853e113 Mon Sep 17 00:00:00 2001 From: gerblesh <101901964+gerblesh@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:03:57 -0800 Subject: [PATCH] initial commit --- .gitignore | 1 + checks/hardware.go | 185 +++++++++++++++++++++++++++++++++++++++++++++ cmd/hw-check.go | 25 ++++++ cmd/root.go | 47 ++++++++++++ cmd/update.go | 54 +++++++++++++ cmd/updateCheck.go | 15 ++++ cmd/wait.go | 34 +++++++++ drv/bootc.go | 24 ++++++ drv/brew.go | 56 ++++++++++++++ drv/flatpak.go | 15 ++++ go.mod | 22 ++++++ go.sum | 45 +++++++++++ lib/filelock.go | 19 +++++ lib/session.go | 69 +++++++++++++++++ main.go | 7 ++ 15 files changed, 618 insertions(+) create mode 100644 checks/hardware.go create mode 100644 cmd/hw-check.go create mode 100644 cmd/root.go create mode 100644 cmd/update.go create mode 100644 cmd/updateCheck.go create mode 100644 cmd/wait.go create mode 100644 drv/bootc.go create mode 100644 drv/brew.go create mode 100644 drv/flatpak.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib/filelock.go create mode 100644 lib/session.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore index 6f72f89..3f3f572 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ go.work.sum # env file .env +ublue-upd diff --git a/checks/hardware.go b/checks/hardware.go new file mode 100644 index 0000000..d27c340 --- /dev/null +++ b/checks/hardware.go @@ -0,0 +1,185 @@ +package checks + +import ( + "fmt" + + "github.com/godbus/dbus/v5" + "github.com/shirou/gopsutil/v4/load" + "github.com/shirou/gopsutil/v4/mem" +) + +type Info struct { + Name string + Err error +} + +func Hardware(conn *dbus.Conn) []Info { + var checks []Info + checks = append(checks, battery(conn)) + checks = append(checks, network(conn)) + checks = append(checks, cpu()) + checks = append(checks, memory()) + + return checks +} + +func battery(conn *dbus.Conn) Info { + const name string = "Battery" + upower := conn.Object("org.freedesktop.UPower", "/org/freedesktop/UPower") + // first, check if the device is running on battery + variant, err := upower.GetProperty("org.freedesktop.UPower.OnBattery") + if err != nil { + return Info{ + name, + err, + } + } + + onBattery, ok := variant.Value().(bool) + if !ok { + return Info{ + name, + fmt.Errorf("Unable to determine if this computer is running on battery with: %v", variant), + } + } + // Not running on battery, skip this test + if !onBattery { + return Info{ + name, + nil, + } + } + + dev := conn.Object("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice") + variant, err = dev.GetProperty("org.freedesktop.UPower.Device.Percentage") + if err != nil { + return Info{ + name, + err, + } + } + + batteryPercent, ok := variant.Value().(float64) + + if !ok { + return Info{ + name, + fmt.Errorf("Unable to get battery percent from: %v", variant), + } + } + if batteryPercent < 20 { + return Info{ + name, + fmt.Errorf("Battery percent below 20, detected battery percent: %v", batteryPercent), + } + } + + return Info{ + name, + nil, + } +} + +func network(conn *dbus.Conn) Info { + const name string = "Network" + + nm := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") + + variant, err := nm.GetProperty("org.freedesktop.NetworkManager.Metered") + if err != nil { + return Info{ + name, + err, + } + } + metered, ok := variant.Value().(uint32) + if !ok { + return Info{ + name, + fmt.Errorf("Unable to determine if network connection is metered from: %v", variant), + } + } + // The possible values of "Metered" are documented here: + // https://networkmanager.dev/docs/api/latest/nm-dbus-types.html//NMMetered + // + // NM_METERED_UNKNOWN = 0 // The metered status is unknown + // NM_METERED_YES = 1 // Metered, the value was explicitly configured + // NM_METERED_NO = 2 // Not metered, the value was explicitly configured + // NM_METERED_GUESS_YES = 3 // Metered, the value was guessed + // NM_METERED_GUESS_NO = 4 // Not metered, the value was guessed + // + if metered == 1 || metered == 3 { + return Info{ + name, + fmt.Errorf("Network is metered"), + } + } + + // check if user is connected to network + var connectivity uint32 + err = nm.Call("org.freedesktop.NetworkManager.CheckConnectivity", 0).Store(&connectivity) + if err != nil { + return Info{ + name, + err, + } + } + + // 4 means fully connected: https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMConnectivityState + if connectivity != 4 { + return Info{ + name, + fmt.Errorf("Network not online"), + } + } + + return Info{ + name, + nil, + } + +} + +func memory() Info { + const name string = "Memory" + v, err := mem.VirtualMemory() + if err != nil { + return Info{ + name, + err, + } + } + if v.UsedPercent > 90.0 { + return Info{ + name, + fmt.Errorf("Current memory usage above 90 percent: %v", v.UsedPercent), + } + } + return Info{ + name, + nil, + } +} + +func cpu() Info { + const name string = "CPU" + avg, err := load.Avg() + if err != nil { + return Info{ + name, + err, + } + } + // Check if the CPU load in the 5 minutes was greater than 50% + if avg.Load5 > 50.0 { + return Info{ + name, + fmt.Errorf("CPU load above 50 percent: %v", avg.Load5), + } + } + + return Info{ + name, + nil, + } +} diff --git a/cmd/hw-check.go b/cmd/hw-check.go new file mode 100644 index 0000000..66efdba --- /dev/null +++ b/cmd/hw-check.go @@ -0,0 +1,25 @@ +package cmd + +import ( + "log" + + "github.com/gerblesh/update-next/checks" + "github.com/godbus/dbus/v5" + "github.com/spf13/cobra" +) + +func HwCheck(cmd *cobra.Command, args []string) { + // (some hardware checks require dbus access) + conn, err := dbus.SystemBus() + if err != nil { + log.Fatalf("Failed to connect to session bus: %v", err) + } + defer conn.Close() + checkInfo := checks.Hardware(conn) + for _, info := range checkInfo { + if info.Err != nil { + log.Fatalf("Hardware checks failed for %s, returned error: %v", info.Name, info.Err) + } + } + log.Println("Hardware checks passed") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..66122ca --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +var ( + rootCmd = &cobra.Command{ + Use: "ublue-upd", + Short: "ublue-upd is the successor to ublue-update, built for bootc", + Run: Update, + } + + waitCmd = &cobra.Command{ + Use: "wait", + Short: "Wait for a condition or time period", + Long: "This command simulates waiting for some event or process to complete.", + Run: Wait, + } + + updateCheckCmd = &cobra.Command{ + Use: "update-check", + Short: "Check for updates to the booted image", + Run: UpdateCheck, + } + + hardwareCheckCmd = &cobra.Command{ + Use: "hw-check", + Short: "Run hardware checks", + Run: HwCheck, + } +) + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func init() { + rootCmd.AddCommand(waitCmd) + rootCmd.AddCommand(updateCheckCmd) + rootCmd.AddCommand(hardwareCheckCmd) +} diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..b377546 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "github.com/gerblesh/update-next/checks" + "github.com/gerblesh/update-next/drv" + "github.com/gerblesh/update-next/lib" + "github.com/godbus/dbus/v5" + // "github.com/schollz/progressbar/v3" + "github.com/spf13/cobra" + "log" +) + +func Update(cmd *cobra.Command, args []string) { + + // (some hardware checks require dbus access) + conn, err := dbus.SystemBus() + if err != nil { + log.Fatalf("Failed to connect to session bus: %v", err) + } + defer conn.Close() + checkInfo := checks.Hardware(conn) + for _, info := range checkInfo { + if info.Err != nil { + log.Fatalf("Hardware checks failed for %s, returned error: %v", info.Name, info.Err) + } + } + log.Println("Hardware checks passed") + + updateAvailable, err := drv.CheckForUpdate() + if err != nil { + log.Fatalf("Failed to check for image updates: %v", err) + } + log.Printf("update available: %v", updateAvailable) + if updateAvailable { + err = drv.BootcUpdate() + if err != nil { + log.Fatalf("Failed to update system: %v", err) + } + } + err = drv.BrewUpdate() + if err != nil { + log.Fatalf("Failed to update brew: %v", err) + } + + users, err := lib.ListUsers() + if err != nil { + log.Fatalf("ERROR: %v", err) + } + err = drv.FlatpakUpdate(users) + if err != nil { + log.Fatalf("Failed to update flatpak: %v", err) + } + +} diff --git a/cmd/updateCheck.go b/cmd/updateCheck.go new file mode 100644 index 0000000..46df558 --- /dev/null +++ b/cmd/updateCheck.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/gerblesh/update-next/drv" + "github.com/spf13/cobra" + "log" +) + +func UpdateCheck(cmd *cobra.Command, args []string) { + update, err := drv.CheckForUpdate() + if err != nil { + log.Fatalf("Failed to check for updates: %v", err) + } + log.Printf("Update Available: %v", update) +} diff --git a/cmd/wait.go b/cmd/wait.go new file mode 100644 index 0000000..831cc90 --- /dev/null +++ b/cmd/wait.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "log" + "os" + "time" + + "github.com/gerblesh/update-next/lib" + "github.com/spf13/cobra" +) + +func Wait(cmd *cobra.Command, args []string) { + // TODO: rely on bootc to do transaction wait + lockFilePath := "/sysroot/ostree/lock" + + for { + + time.Sleep(2 * time.Second) + file, err := os.Open(lockFilePath) + if err != nil { + // file must not exist + break + } + + if lib.IsFileLocked(file) { + file.Close() + log.Printf("Waiting for lockfile: %s", lockFilePath) + } else { + file.Close() + break + } + } + log.Printf("Done Waiting!") +} diff --git a/drv/bootc.go b/drv/bootc.go new file mode 100644 index 0000000..01a57ac --- /dev/null +++ b/drv/bootc.go @@ -0,0 +1,24 @@ +package drv + +import ( + "os/exec" + "strings" +) + +func BootcUpdate() error { + cmd := exec.Command("/usr/bin/bootc", "upgrade") + err := cmd.Run() + if err != nil { + return err + } + return nil +} + +func CheckForUpdate() (bool, error) { + cmd := exec.Command("/usr/bin/bootc", "upgrade", "--check") + out, err := cmd.Output() + if err != nil { + return true, err + } + return !strings.Contains(string(out), "No changes in:"), nil +} diff --git a/drv/brew.go b/drv/brew.go new file mode 100644 index 0000000..7bb4104 --- /dev/null +++ b/drv/brew.go @@ -0,0 +1,56 @@ +package drv + +import ( + "fmt" + "github.com/gerblesh/update-next/lib" + "log" + "os" + "syscall" +) + +var ( + brewPrefix = "/home/linuxbrew/.linuxbrew" + brewCellar = fmt.Sprintf("%s/Cellar", brewPrefix) + brewRepo = fmt.Sprintf("%s/Homebrew", brewPrefix) + brewPath = fmt.Sprintf("%s/bin/brew", brewPrefix) +) + +func getBrewUID() (int, error) { + inf, err := os.Stat(brewPrefix) + if err != nil { + log.Printf("Unable to stat: %v, got error: %v", brewPrefix, err) + return -1, err + } + + if !inf.IsDir() { + return -1, fmt.Errorf("Brew prefix: %v, is not a dir.", brewPrefix) + } + stat, ok := inf.Sys().(*syscall.Stat_t) + if !ok { + return -1, fmt.Errorf("Unable to retriev UID info for %v", brewPrefix) + } + return int(stat.Uid), nil +} + +func BrewUpdate() error { + uid, err := getBrewUID() + if err != nil { + return err + } + env := map[string]string{ + "HOMEBREW_PREFIX": brewPrefix, + "HOMEBREW_REPOSITORY": brewRepo, + "HOMEBREW_CELLAR": brewCellar, + } + _, err = lib.RunUID(uid, []string{brewPath, "update"}, env) + if err != nil { + return err + } + + _, err = lib.RunUID(uid, []string{brewPath, "upgrade"}, env) + if err != nil { + return err + } + + return nil +} diff --git a/drv/flatpak.go b/drv/flatpak.go new file mode 100644 index 0000000..25182e6 --- /dev/null +++ b/drv/flatpak.go @@ -0,0 +1,15 @@ +package drv + +import ( + "github.com/gerblesh/update-next/lib" + "os/exec" +) + +func FlatpakUpdate(users []lib.User) error { + cmd := exec.Command("/usr/bin/flatpak", "update") + _, err := cmd.Output() + if err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5f1f781 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module github.com/gerblesh/update-next + +go 1.23.3 + +require ( + github.com/godbus/dbus/v5 v5.1.0 + github.com/shirou/gopsutil/v4 v4.24.10 + github.com/spf13/cobra v1.8.1 +) + +require ( + github.com/ebitengine/purego v0.8.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.27.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c09fbd9 --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= +github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM= +github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/filelock.go b/lib/filelock.go new file mode 100644 index 0000000..34fe3cb --- /dev/null +++ b/lib/filelock.go @@ -0,0 +1,19 @@ +package lib + +import ( + "io" + "os" + "syscall" +) + +func IsFileLocked(file *os.File) bool { + lock := syscall.Flock_t{ + Type: syscall.F_WRLCK, + Whence: io.SeekStart, + } + err := syscall.FcntlFlock(file.Fd(), syscall.F_GETLK, &lock) + if err != nil { + return false + } + return lock.Type != syscall.F_UNLCK +} diff --git a/lib/session.go b/lib/session.go new file mode 100644 index 0000000..25b6e4a --- /dev/null +++ b/lib/session.go @@ -0,0 +1,69 @@ +package lib + +import ( + "fmt" + "github.com/godbus/dbus/v5" + "os/exec" +) + +type User struct { + UID int + Name string +} + +func RunUID(uid int, command []string, env map[string]string) ([]byte, error) { + // Just fork systemd-run, using the systemd API gave me a massive headache + cmdArgs := []string{ + "/usr/bin/systemd-run", + "--user", + "--machine", + fmt.Sprintf("%d@", uid), + "--pipe", + "--quiet", + } + cmdArgs = append(cmdArgs, command...) + + cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) + + return cmd.CombinedOutput() +} + +func ListUsers() ([]User, error) { + conn, err := dbus.SystemBus() + if err != nil { + return []User{}, fmt.Errorf("failed to connect to system bus: %v", err) + } + defer conn.Close() + + var resp [][]dbus.Variant + object := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1") + err = object.Call("org.freedesktop.login1.Manager.ListUsers", 0).Store(&resp) + if err != nil { + return []User{}, err + } + + var users []User + for _, data := range resp { + if len(data) < 2 { + return []User{}, fmt.Errorf("Malformed dbus response") + } + uidVariant := data[0] + nameVariant := data[1] + + uid, ok := uidVariant.Value().(uint32) + if !ok { + return []User{}, fmt.Errorf("invalid UID type, expected uint32") + } + + name, ok := nameVariant.Value().(string) + if !ok { + return []User{}, fmt.Errorf("invalid Name type, expected string") + } + + users = append(users, User{ + UID: int(uid), + Name: name, + }) + } + return users, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..af82a36 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/gerblesh/update-next/cmd" + +func main() { + cmd.Execute() +}