From db21945550c6ac6473dc793de150a42526cb4f19 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Tue, 28 Apr 2026 11:05:28 +0200 Subject: [PATCH 1/3] feat: implement machine inspect This implements 'machine inspect' with the possiblity to list all machines. % uc machine inspect machine-1 ```json { "machine": { "id": "69d30595b7516a2b61034d270ea663e7", "name": "machine-1", "network": { "subnet": { "ip": "10.210.0.0", "bits": 24 }, "management_ip": "fdcc:c2f7:e155:c8d9:2b3:da5f:2237:2732", "endpoints": [ "172.21.0.2:51820" ], "public_key": "wvfhVcjZArPaXyI3JzIhWgDRW1tuF2KbbMD0L2XMqSE=" } }, "state": 1 } ``` all machines (as an array) ```json { "machines": [ { "machine": { "id": "69d30595b7516a2b61034d270ea663e7", "name": "machine-1", "network": { "subnet": { "ip": "10.210.0.0", "bits": 24 }, "management_ip": "fdcc:c2f7:e155:c8d9:2b3:da5f:2237:2732", "endpoints": [ "172.21.0.2:51820" ], "public_key": "wvfhVcjZArPaXyI3JzIhWgDRW1tuF2KbbMD0L2XMqSE=" } }, "state": 1 } ] } ``` For IP and IPPort MarshalJSON method are added to generate the correct JSON. ls.go use cmp.Or to make the code even smaller. Signed-off-by: Miek Gieben --- cmd/uncloud/machine/inspect.go | 76 ++++++++++++++++++++++ cmd/uncloud/machine/ls.go | 12 +--- cmd/uncloud/machine/root.go | 1 + internal/machine/api/pb/common.go | 26 ++++++++ website/docs/9-cli-reference/uc_machine.md | 1 + 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 cmd/uncloud/machine/inspect.go diff --git a/cmd/uncloud/machine/inspect.go b/cmd/uncloud/machine/inspect.go new file mode 100644 index 00000000..114f7aaa --- /dev/null +++ b/cmd/uncloud/machine/inspect.go @@ -0,0 +1,76 @@ +package machine + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/psviderski/uncloud/pkg/api" + "github.com/spf13/cobra" +) + +func NewInspectCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect [MACHINE]", + Short: "Display detailed information of a machine. Without an argument it shows all machines.", + RunE: func(cmd *cobra.Command, args []string) error { + uncli := cmd.Context().Value("cli").(*cli.CLI) + if len(args) == 0 { + return inspectAll(cmd.Context(), uncli) + } + return inspect(cmd.Context(), uncli, args[0]) + }, + } + + return cmd +} + +func inspect(ctx context.Context, uncli *cli.CLI, name string) error { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return fmt.Errorf("connect to cluster: %w", err) + } + defer client.Close() + + machines, err := client.ListMachines(ctx, &api.MachineFilter{NamesOrIDs: []string{name}}) + if err != nil { + return fmt.Errorf("list machines: %w", err) + } + + if len(machines) == 0 { + return fmt.Errorf("machine '%s' not found", name) + } + data, err := json.MarshalIndent(machines[0], "", " ") + if err != nil { + return fmt.Errorf("marshal machines %w", err) + } + fmt.Println(string(data)) + + return nil +} + +func inspectAll(ctx context.Context, uncli *cli.CLI) error { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return fmt.Errorf("connect to cluster: %w", err) + } + defer client.Close() + + machines, err := client.ListMachines(ctx, &api.MachineFilter{}) + if err != nil { + return fmt.Errorf("list machines: %w", err) + } + + // Wrap in Machines type to create array of Machines. + type Machines struct { + Machines api.MachineMembersList `json:"machines"` + } + data, err := json.MarshalIndent(Machines{machines}, "", " ") + if err != nil { + return fmt.Errorf("marshal machines: %w", err) + } + fmt.Println(string(data)) + + return nil +} diff --git a/cmd/uncloud/machine/ls.go b/cmd/uncloud/machine/ls.go index e04c15d8..7a99eca8 100644 --- a/cmd/uncloud/machine/ls.go +++ b/cmd/uncloud/machine/ls.go @@ -1,6 +1,7 @@ package machine import ( + "cmp" "context" "fmt" "net/netip" @@ -46,23 +47,16 @@ func list(ctx context.Context, uncli *cli.CLI) error { subnet, _ := m.Network.Subnet.ToPrefix() subnet = netip.PrefixFrom(network.MachineIP(subnet), subnet.Bits()) - publicIP := "-" - if m.PublicIp != nil { - ip, _ := m.PublicIp.ToAddr() - publicIP = ip.String() - } - endpoints := make([]string, len(m.Network.Endpoints)) for i, ep := range m.Network.Endpoints { - addrPort, _ := ep.ToAddrPort() - endpoints[i] = addrPort.String() + endpoints[i] = ep.ToString() } t.Row( m.Name, capitalise(member.State.String()), subnet.String(), - publicIP, + cmp.Or(m.PublicIp.ToString(), "-"), strings.Join(endpoints, tui.Faint.Render(", ")), member.Machine.Id, ) diff --git a/cmd/uncloud/machine/root.go b/cmd/uncloud/machine/root.go index 3bb70443..968a7a1e 100644 --- a/cmd/uncloud/machine/root.go +++ b/cmd/uncloud/machine/root.go @@ -13,6 +13,7 @@ func NewRootCommand() *cobra.Command { cmd.AddCommand( NewAddCommand(), NewInitCommand(), + NewInspectCommand(), NewListCommand(), NewLogsCommand(), NewRenameCommand(), diff --git a/internal/machine/api/pb/common.go b/internal/machine/api/pb/common.go index 8eea6fad..c9dac649 100644 --- a/internal/machine/api/pb/common.go +++ b/internal/machine/api/pb/common.go @@ -23,10 +23,23 @@ func (ip *IP) ToAddr() (netip.Addr, error) { return addr, nil } +func (ip *IP) ToString() string { + // Has to be ToString, because String is generated (and doesn't do the right thing). + if ip == nil { + return "" + } + addr, _ := ip.ToAddr() + return addr.String() +} + func (ip *IP) Equal(other *IP) bool { return bytes.Equal(ip.Ip, other.Ip) } +func (ip *IP) MarshalJSON() ([]byte, error) { + return []byte("\"" + ip.ToString() + "\""), nil +} + func NewIPPort(ap netip.AddrPort) *IPPort { return &IPPort{Ip: NewIP(ap.Addr()), Port: uint32(ap.Port())} } @@ -39,6 +52,19 @@ func (ipp *IPPort) ToAddrPort() (netip.AddrPort, error) { return netip.AddrPortFrom(addr, uint16(ipp.Port)), nil } +func (ipp *IPPort) ToString() string { + // Has to be ToString, because String is generated (and doesn't do the right thing). + if ipp == nil { + return "" + } + addrPort, _ := ipp.ToAddrPort() + return addrPort.String() +} + +func (ipp *IPPort) MarshalJSON() ([]byte, error) { + return []byte("\"" + ipp.ToString() + "\""), nil +} + func NewIPPrefix(p netip.Prefix) *IPPrefix { return &IPPrefix{Ip: NewIP(p.Addr()), Bits: uint32(p.Bits())} } diff --git a/website/docs/9-cli-reference/uc_machine.md b/website/docs/9-cli-reference/uc_machine.md index 98d936b4..b39f43fe 100644 --- a/website/docs/9-cli-reference/uc_machine.md +++ b/website/docs/9-cli-reference/uc_machine.md @@ -22,6 +22,7 @@ Manage machines in the cluster. * [uc](uc.md) - A CLI tool for managing Uncloud resources such as machines, services, and volumes. * [uc machine add](uc_machine_add.md) - Add a remote machine to a cluster. * [uc machine init](uc_machine_init.md) - Initialise a new cluster with a remote machine as the first member. +* [uc machine inspect](uc_machine_inspect.md) - Display detailed information of a machine. Without an argument it shows all machines. * [uc machine logs](uc_machine_logs.md) - View systemd service logs. * [uc machine ls](uc_machine_ls.md) - List machines in a cluster. * [uc machine rename](uc_machine_rename.md) - Rename a machine in the cluster. From c7a67254db0d83da68e1ca2e6f0d3b73d8f2dd9a Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 29 Apr 2026 08:10:54 +0200 Subject: [PATCH 2/3] Fix maximum arg for ls subcommands Signed-off-by: Miek Gieben --- cmd/uncloud/context/ls.go | 1 + cmd/uncloud/machine/inspect.go | 1 + cmd/uncloud/machine/ls.go | 1 + cmd/uncloud/service/inspect.go | 6 +++--- cmd/uncloud/service/ls.go | 1 + cmd/uncloud/volume/inspect.go | 32 ++++++++++++++++++++++++++++++-- cmd/uncloud/volume/ls.go | 1 + 7 files changed, 38 insertions(+), 5 deletions(-) diff --git a/cmd/uncloud/context/ls.go b/cmd/uncloud/context/ls.go index e9eb4b98..cb9949d5 100644 --- a/cmd/uncloud/context/ls.go +++ b/cmd/uncloud/context/ls.go @@ -15,6 +15,7 @@ func NewListCommand() *cobra.Command { Use: "ls", Aliases: []string{"list"}, Short: "List available cluster contexts.", + Args: cobra.MaximumNArgs(0), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) return list(uncli) diff --git a/cmd/uncloud/machine/inspect.go b/cmd/uncloud/machine/inspect.go index 114f7aaa..230b7ee8 100644 --- a/cmd/uncloud/machine/inspect.go +++ b/cmd/uncloud/machine/inspect.go @@ -14,6 +14,7 @@ func NewInspectCommand() *cobra.Command { cmd := &cobra.Command{ Use: "inspect [MACHINE]", Short: "Display detailed information of a machine. Without an argument it shows all machines.", + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) if len(args) == 0 { diff --git a/cmd/uncloud/machine/ls.go b/cmd/uncloud/machine/ls.go index 7a99eca8..c47d7900 100644 --- a/cmd/uncloud/machine/ls.go +++ b/cmd/uncloud/machine/ls.go @@ -18,6 +18,7 @@ func NewListCommand() *cobra.Command { Use: "ls", Aliases: []string{"list"}, Short: "List machines in a cluster.", + Args: cobra.MaximumNArgs(0), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) return list(cmd.Context(), uncli) diff --git a/cmd/uncloud/service/inspect.go b/cmd/uncloud/service/inspect.go index 0fb7e59d..11c70505 100644 --- a/cmd/uncloud/service/inspect.go +++ b/cmd/uncloud/service/inspect.go @@ -21,9 +21,9 @@ type inspectOptions struct { func NewInspectCommand(groupID string) *cobra.Command { opts := inspectOptions{} cmd := &cobra.Command{ - Use: "inspect SERVICE", - Short: "Display detailed information on a service.", - Args: cobra.ExactArgs(1), + Use: "inspect [SERVICE]", + Short: "Display detailed information on a service. Without arguments it shows all services.", + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) opts.service = args[0] diff --git a/cmd/uncloud/service/ls.go b/cmd/uncloud/service/ls.go index 1ccca21f..083683c3 100644 --- a/cmd/uncloud/service/ls.go +++ b/cmd/uncloud/service/ls.go @@ -17,6 +17,7 @@ func NewListCommand(groupID string) *cobra.Command { Use: "ls", Aliases: []string{"list"}, Short: "List services.", + Args: cobra.MaximumNArgs(0), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) return list(cmd.Context(), uncli) diff --git a/cmd/uncloud/volume/inspect.go b/cmd/uncloud/volume/inspect.go index dd8f66e0..e4aab948 100644 --- a/cmd/uncloud/volume/inspect.go +++ b/cmd/uncloud/volume/inspect.go @@ -20,10 +20,13 @@ func NewInspectCommand() *cobra.Command { cmd := &cobra.Command{ Use: "inspect VOLUME_NAME", - Short: "Display detailed information on a volume.", - Args: cobra.ExactArgs(1), + Short: "Display detailed information on a volume. Without an argument it shows all volumes.", + Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) + if len(args) == 0 { + return inspectAll(cmd.Context(), uncli) + } return inspect(cmd.Context(), uncli, args[0], opts) }, } @@ -76,3 +79,28 @@ func inspect(ctx context.Context, uncli *cli.CLI, name string, opts inspectOptio return nil } + +func inspectAll(ctx context.Context, uncli *cli.CLI) error { + client, err := uncli.ConnectCluster(ctx) + if err != nil { + return fmt.Errorf("connect to cluster: %w", err) + } + defer client.Close() + + volumes, err := client.ListVolumes(ctx, &api.VolumeFilter{}) + if err != nil { + return fmt.Errorf("list volumes: %w", err) + } + + // Wrap in Volumes type to create array of Volumes. + type Volumes struct { + Volumes []api.MachineVolume `json:"volumes"` + } + data, err := json.MarshalIndent(Volumes{volumes}, "", " ") + if err != nil { + return fmt.Errorf("marshal volumes: %w", err) + } + fmt.Println(string(data)) + + return nil +} diff --git a/cmd/uncloud/volume/ls.go b/cmd/uncloud/volume/ls.go index 732ef495..87852c61 100644 --- a/cmd/uncloud/volume/ls.go +++ b/cmd/uncloud/volume/ls.go @@ -24,6 +24,7 @@ func NewListCommand() *cobra.Command { Use: "ls", Aliases: []string{"list"}, Short: "List volumes across all machines in the cluster.", + Args: cobra.MaximumNArgs(0), RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) return list(cmd.Context(), uncli, opts) From 9cb43693b6ea570577bf42e7b37bf4da6998e8dd Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 29 Apr 2026 08:11:43 +0200 Subject: [PATCH 3/3] docs Signed-off-by: Miek Gieben --- website/docs/9-cli-reference/uc.md | 2 +- website/docs/9-cli-reference/uc_inspect.md | 4 ++-- website/docs/9-cli-reference/uc_service.md | 2 +- website/docs/9-cli-reference/uc_service_inspect.md | 4 ++-- website/docs/9-cli-reference/uc_volume.md | 2 +- website/docs/9-cli-reference/uc_volume_inspect.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/docs/9-cli-reference/uc.md b/website/docs/9-cli-reference/uc.md index 4226e265..e3971bd1 100644 --- a/website/docs/9-cli-reference/uc.md +++ b/website/docs/9-cli-reference/uc.md @@ -22,7 +22,7 @@ A CLI tool for managing Uncloud resources such as machines, services, and volume * [uc exec](uc_exec.md) - Execute a command in a running service container. * [uc image](uc_image.md) - Manage images on machines in the cluster. * [uc images](uc_images.md) - List images on machines in the cluster. -* [uc inspect](uc_inspect.md) - Display detailed information on a service. +* [uc inspect](uc_inspect.md) - Display detailed information on a service. Without arguments it shows all services. * [uc logs](uc_logs.md) - View service logs. * [uc ls](uc_ls.md) - List services. * [uc machine](uc_machine.md) - Manage machines in the cluster. diff --git a/website/docs/9-cli-reference/uc_inspect.md b/website/docs/9-cli-reference/uc_inspect.md index d5993171..83825cb1 100644 --- a/website/docs/9-cli-reference/uc_inspect.md +++ b/website/docs/9-cli-reference/uc_inspect.md @@ -1,9 +1,9 @@ # uc inspect -Display detailed information on a service. +Display detailed information on a service. Without arguments it shows all services. ``` -uc inspect SERVICE [flags] +uc inspect [SERVICE] [flags] ``` ## Options diff --git a/website/docs/9-cli-reference/uc_service.md b/website/docs/9-cli-reference/uc_service.md index c3613d96..42bd700d 100644 --- a/website/docs/9-cli-reference/uc_service.md +++ b/website/docs/9-cli-reference/uc_service.md @@ -21,7 +21,7 @@ Manage services in the cluster. * [uc](uc.md) - A CLI tool for managing Uncloud resources such as machines, services, and volumes. * [uc service exec](uc_service_exec.md) - Execute a command in a running service container. -* [uc service inspect](uc_service_inspect.md) - Display detailed information on a service. +* [uc service inspect](uc_service_inspect.md) - Display detailed information on a service. Without arguments it shows all services. * [uc service logs](uc_service_logs.md) - View service logs. * [uc service ls](uc_service_ls.md) - List services. * [uc service rm](uc_service_rm.md) - Remove one or more services. diff --git a/website/docs/9-cli-reference/uc_service_inspect.md b/website/docs/9-cli-reference/uc_service_inspect.md index 159e1164..5d447d6c 100644 --- a/website/docs/9-cli-reference/uc_service_inspect.md +++ b/website/docs/9-cli-reference/uc_service_inspect.md @@ -1,9 +1,9 @@ # uc service inspect -Display detailed information on a service. +Display detailed information on a service. Without arguments it shows all services. ``` -uc service inspect SERVICE [flags] +uc service inspect [SERVICE] [flags] ``` ## Options diff --git a/website/docs/9-cli-reference/uc_volume.md b/website/docs/9-cli-reference/uc_volume.md index b741b694..7ba89ef0 100644 --- a/website/docs/9-cli-reference/uc_volume.md +++ b/website/docs/9-cli-reference/uc_volume.md @@ -21,7 +21,7 @@ Manage volumes in the cluster. * [uc](uc.md) - A CLI tool for managing Uncloud resources such as machines, services, and volumes. * [uc volume create](uc_volume_create.md) - Create a volume on a specific machine. -* [uc volume inspect](uc_volume_inspect.md) - Display detailed information on a volume. +* [uc volume inspect](uc_volume_inspect.md) - Display detailed information on a volume. Without an argument it shows all volumes. * [uc volume ls](uc_volume_ls.md) - List volumes across all machines in the cluster. * [uc volume rm](uc_volume_rm.md) - Remove one or more volumes. diff --git a/website/docs/9-cli-reference/uc_volume_inspect.md b/website/docs/9-cli-reference/uc_volume_inspect.md index e6e97ea7..fdc70ff4 100644 --- a/website/docs/9-cli-reference/uc_volume_inspect.md +++ b/website/docs/9-cli-reference/uc_volume_inspect.md @@ -1,6 +1,6 @@ # uc volume inspect -Display detailed information on a volume. +Display detailed information on a volume. Without an argument it shows all volumes. ``` uc volume inspect VOLUME_NAME [flags]