Skip to content

Commit

Permalink
Add switch ssh|console and show more details (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
majst01 authored Feb 27, 2023
1 parent 2dc8c95 commit 28aa9bb
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

steps:
- name: Log in to the container registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
Expand Down
18 changes: 18 additions & 0 deletions cmd/completion/switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package completion

import (
"github.com/metal-stack/metal-go/api/client/switch_operations"
"github.com/spf13/cobra"
)

func (c *Completion) SwitchListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
resp, err := c.client.SwitchOperations().ListSwitches(switch_operations.NewListSwitchesParams(), nil)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, s := range resp.Payload {
names = append(names, *s.ID)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
34 changes: 17 additions & 17 deletions cmd/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,25 +176,25 @@ func Test_MachineCmd_MultiResult(t *testing.T) {
machine1,
},
wantTable: pointer.Pointer(`
ID LAST EVENT WHEN AGE HOSTNAME PROJECT SIZE IMAGE PARTITION
2 Waiting 1m 1 1
1 Phoned Home 7d 14d machine-hostname-1 project-1 1 debian-name 1
ID LAST EVENT WHEN AGE HOSTNAME PROJECT SIZE IMAGE PARTITION RACK
2 Waiting 1m 1 1 rack-1
1 Phoned Home 7d 14d machine-hostname-1 project-1 1 debian-name 1 rack-1
`),
wantWideTable: pointer.Pointer(`
ID LAST EVENT WHEN AGE DESCRIPTION NAME HOSTNAME PROJECT IPS SIZE IMAGE PARTITION STARTED TAGS LOCK/RESERVE
2 Waiting 1m 1 1 b
1 Phoned Home 7d 14d machine allocation 1 machine-1 machine-hostname-1 project-1 1.1.1.1 1 debian-name 1 2022-05-05T01:02:03Z a
ID LAST EVENT WHEN AGE DESCRIPTION NAME HOSTNAME PROJECT IPS SIZE IMAGE PARTITION RACK STARTED TAGS LOCK/RESERVE
2 Waiting 1m 1 1 rack-1 b
1 Phoned Home 7d 14d machine allocation 1 machine-1 machine-hostname-1 project-1 1.1.1.1 1 debian-name 1 rack-1 2022-05-05T01:02:03Z a
`),
template: pointer.Pointer("{{ .id }} {{ .name }}"),
wantTemplate: pointer.Pointer(`
2 machine-2
1 machine-1
`),
wantMarkdown: pointer.Pointer(`
| ID | | LAST EVENT | WHEN | AGE | HOSTNAME | PROJECT | SIZE | IMAGE | PARTITION |
|----|--|-------------|------|-----|--------------------|-----------|------|-------------|-----------|
| 2 | | Waiting | 1m | | | | 1 | | 1 |
| 1 | | Phoned Home | 7d | 14d | machine-hostname-1 | project-1 | 1 | debian-name | 1 |
| ID | | LAST EVENT | WHEN | AGE | HOSTNAME | PROJECT | SIZE | IMAGE | PARTITION | RACK |
|----|--|-------------|------|-----|--------------------|-----------|------|-------------|-----------|--------|
| 2 | | Waiting | 1m | | | | 1 | | 1 | rack-1 |
| 1 | | Phoned Home | 7d | 14d | machine-hostname-1 | project-1 | 1 | debian-name | 1 | rack-1 |
`),
},
}
Expand All @@ -219,21 +219,21 @@ func Test_MachineCmd_SingleResult(t *testing.T) {
},
want: machine1,
wantTable: pointer.Pointer(`
ID LAST EVENT WHEN AGE HOSTNAME PROJECT SIZE IMAGE PARTITION
1 Phoned Home 7d 14d machine-hostname-1 project-1 1 debian-name 1
ID LAST EVENT WHEN AGE HOSTNAME PROJECT SIZE IMAGE PARTITION RACK
1 Phoned Home 7d 14d machine-hostname-1 project-1 1 debian-name 1 rack-1
`),
wantWideTable: pointer.Pointer(`
ID LAST EVENT WHEN AGE DESCRIPTION NAME HOSTNAME PROJECT IPS SIZE IMAGE PARTITION STARTED TAGS LOCK/RESERVE
1 Phoned Home 7d 14d machine allocation 1 machine-1 machine-hostname-1 project-1 1.1.1.1 1 debian-name 1 2022-05-05T01:02:03Z a
ID LAST EVENT WHEN AGE DESCRIPTION NAME HOSTNAME PROJECT IPS SIZE IMAGE PARTITION RACK STARTED TAGS LOCK/RESERVE
1 Phoned Home 7d 14d machine allocation 1 machine-1 machine-hostname-1 project-1 1.1.1.1 1 debian-name 1 rack-1 2022-05-05T01:02:03Z a
`),
template: pointer.Pointer("{{ .id }} {{ .name }}"),
wantTemplate: pointer.Pointer(`
1 machine-1
`),
wantMarkdown: pointer.Pointer(`
| ID | | LAST EVENT | WHEN | AGE | HOSTNAME | PROJECT | SIZE | IMAGE | PARTITION |
|----|--|-------------|------|-----|--------------------|-----------|------|-------------|-----------|
| 1 | | Phoned Home | 7d | 14d | machine-hostname-1 | project-1 | 1 | debian-name | 1 |
| ID | | LAST EVENT | WHEN | AGE | HOSTNAME | PROJECT | SIZE | IMAGE | PARTITION | RACK |
|----|--|-------------|------|-----|--------------------|-----------|------|-------------|-----------|--------|
| 1 | | Phoned Home | 7d | 14d | machine-hostname-1 | project-1 | 1 | debian-name | 1 | rack-1 |
`),
},
{
Expand Down
12 changes: 11 additions & 1 deletion cmd/sorters/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ func MachineSorter() *multisort.Sorter[*models.V1MachineResponse] {
bID := p.SafeDeref(p.SafeDeref(b.Partition).ID)
return multisort.Compare(aID, bID, descending)
},
"rack": func(a, b *models.V1MachineResponse, descending bool) multisort.CompareResult {
aID := a.Rackid
bID := b.Rackid
return multisort.Compare(aID, bID, descending)
},
"project": func(a, b *models.V1MachineResponse, descending bool) multisort.CompareResult {
aID := p.SafeDeref(p.SafeDeref(a.Allocation).Project)
bID := p.SafeDeref(p.SafeDeref(b.Allocation).Project)
Expand Down Expand Up @@ -79,6 +84,11 @@ func MachineIPMISorter() *multisort.Sorter[*models.V1MachineIPMIResponse] {
bID := p.SafeDeref(p.SafeDeref(b.Partition).ID)
return multisort.Compare(aID, bID, descending)
},
"rack": func(a, b *models.V1MachineIPMIResponse, descending bool) multisort.CompareResult {
aID := a.Rackid
bID := b.Rackid
return multisort.Compare(aID, bID, descending)
},
"project": func(a, b *models.V1MachineIPMIResponse, descending bool) multisort.CompareResult {
aID := p.SafeDeref(p.SafeDeref(a.Allocation).Project)
bID := p.SafeDeref(p.SafeDeref(b.Allocation).Project)
Expand All @@ -102,5 +112,5 @@ func MachineIPMISorter() *multisort.Sorter[*models.V1MachineIPMIResponse] {
bEvent := p.SafeDeref(p.SafeDeref(p.FirstOrZero(p.SafeDeref(b.Events).Log)).Event)
return multisort.Compare(aEvent, bEvent, descending)
},
}, multisort.Keys{{ID: "partition"}, {ID: "size"}, {ID: "bios"}, {ID: "bmc"}, {ID: "id"}})
}, multisort.Keys{{ID: "partition"}, {ID: "rack"}, {ID: "size"}, {ID: "bios"}, {ID: "bmc"}, {ID: "id"}})
}
131 changes: 110 additions & 21 deletions cmd/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package cmd
import (
"fmt"
"log"
"sort"
"os"
"os/exec"
"strings"

"github.com/metal-stack/metal-go/api/client/switch_operations"
"github.com/metal-stack/metal-go/api/models"
"github.com/metal-stack/metal-lib/pkg/genericcli"
"github.com/metal-stack/metal-lib/pkg/genericcli/printers"
"github.com/metal-stack/metal-lib/pkg/pointer"
"github.com/metal-stack/metalctl/cmd/sorters"
"github.com/metal-stack/metalctl/cmd/tableprinters"
"github.com/spf13/cobra"
Expand All @@ -36,10 +36,12 @@ func newSwitchCmd(c *config) *cobra.Command {
genericcli.DeleteCmd,
genericcli.EditCmd,
),
Aliases: []string{"sw"},
Singular: "switch",
Plural: "switches",
Description: "switch are the leaf switches in the data center that are controlled by metal-stack.",
Sorter: sorters.SwitchSorter(),
ValidArgsFn: c.comp.SwitchListCompletion,
DescribePrinter: func() printers.Printer { return c.describePrinter },
ListPrinter: func() printers.Printer { return c.listPrinter },
}
Expand All @@ -50,6 +52,7 @@ func newSwitchCmd(c *config) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return w.switchDetail()
},
ValidArgsFunction: c.comp.SwitchListCompletion,
}
switchReplaceCmd := &cobra.Command{
Use: "replace <switchID>",
Expand All @@ -69,11 +72,29 @@ Operational steps to replace a switch:
RunE: func(cmd *cobra.Command, args []string) error {
return w.switchReplace(args)
},
ValidArgsFunction: c.comp.SwitchListCompletion,
}
switchSSHCmd := &cobra.Command{
Use: "ssh <switchID>",
Short: "connect to the switch via ssh",
Long: "this requires a network connectivity to the management ip address of the switch.",
RunE: func(cmd *cobra.Command, args []string) error {
return w.switchSSH(args)
},
ValidArgsFunction: c.comp.SwitchListCompletion,
}
switchConsoleCmd := &cobra.Command{
Use: "console <switchID>",
Short: "connect to the switch console",
Long: "this requires a network connectivity to the ip address of the console server this switch is connected to.",
RunE: func(cmd *cobra.Command, args []string) error {
return w.switchConsole(args)
},
ValidArgsFunction: c.comp.SwitchListCompletion,
}

switchDetailCmd.Flags().StringP("filter", "F", "", "filter for site, rack, ID")

return genericcli.NewCmds(cmdsConfig, switchDetailCmd, switchReplaceCmd)
return genericcli.NewCmds(cmdsConfig, switchDetailCmd, switchReplaceCmd, switchSSHCmd, switchConsoleCmd)
}

func (c switchCmd) Get(id string) (*models.V1SwitchResponse, error) {
Expand All @@ -91,13 +112,6 @@ func (c switchCmd) List() ([]*models.V1SwitchResponse, error) {
return nil, err
}

for _, s := range resp.Payload {
s := s
sort.SliceStable(s.Connections, func(i, j int) bool {
return pointer.SafeDeref(pointer.SafeDeref((pointer.SafeDeref(s.Connections[i])).Nic).Name) < pointer.SafeDeref(pointer.SafeDeref((pointer.SafeDeref(s.Connections[j])).Nic).Name)
})
}

return resp.Payload, nil
}

Expand Down Expand Up @@ -132,12 +146,22 @@ func (c switchCmd) ToUpdate(r *models.V1SwitchResponse) (*models.V1SwitchUpdateR
}

func switchResponseToUpdate(r *models.V1SwitchResponse) *models.V1SwitchUpdateRequest {
switchOS := &models.V1SwitchOS{}
if r.Os != nil {
switchOS.Vendor = r.Os.Vendor
switchOS.Vendor = r.Os.Version
}

return &models.V1SwitchUpdateRequest{
Description: r.Description,
ID: r.ID,
Mode: r.Mode,
Name: r.Name,
RackID: r.RackID,
ConsoleCommand: r.ConsoleCommand,
Description: r.Description,
ID: r.ID,
ManagementIP: "",
ManagementUser: "",
Mode: r.Mode,
Name: r.Name,
Os: switchOS,
RackID: r.RackID,
}
}

Expand Down Expand Up @@ -182,16 +206,81 @@ func (c *switchCmd) switchReplace(args []string) error {
return err
}

switchOS := &models.V1SwitchOS{}
if resp.Os != nil {
switchOS.Vendor = resp.Os.Vendor
switchOS.Vendor = resp.Os.Version
}

uresp, err := c.Update(&models.V1SwitchUpdateRequest{
ID: resp.ID,
Name: resp.Name,
Description: resp.Description,
RackID: resp.RackID,
Mode: "replace",
ConsoleCommand: "",
Description: resp.Description,
ID: resp.ID,
ManagementIP: "",
ManagementUser: "",
Mode: "replace",
Name: resp.Name,
Os: switchOS,
RackID: resp.RackID,
})
if err != nil {
return err
}

return c.describePrinter.Print(uresp)
}

func (c *switchCmd) switchSSH(args []string) error {
id, err := genericcli.GetExactlyOneArg(args)
if err != nil {
return err
}

resp, err := c.Get(id)
if err != nil {
return err
}
if resp.ManagementIP == "" || resp.ManagementUser == "" {
return fmt.Errorf("unable to connect to switch by ssh because no ip and user was stored for this switch, please restart metal-core on this switch")
}

// nolint: gosec
cmd := exec.Command("ssh", fmt.Sprintf("%s@%s", resp.ManagementUser, resp.ManagementIP))
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
return cmd.Run()
}

func (c *switchCmd) switchConsole(args []string) error {
id, err := genericcli.GetExactlyOneArg(args)
if err != nil {
return err
}

resp, err := c.Get(id)
if err != nil {
return err
}

if resp.ConsoleCommand == "" {
return fmt.Errorf(`
unable to connect to console because no console_command was specified for this switch
You can add a working console_command to every switch with metalctl switch edit
A sample would look like:
telnet console-server 7008`)
}
parts := strings.Fields(resp.ConsoleCommand)

// nolint: gosec
cmd := exec.Command(parts[0])
if len(parts) > 1 {
// nolint: gosec
cmd = exec.Command(parts[0], parts[1:]...)
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
return cmd.Run()
}
Loading

0 comments on commit 28aa9bb

Please sign in to comment.