Skip to content

Commit

Permalink
add tests for no-keepalive
Browse files Browse the repository at this point in the history
  • Loading branch information
petersutter committed Mar 10, 2023
1 parent a2ab997 commit 78dd0ff
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 98 deletions.
7 changes: 7 additions & 0 deletions pkg/cmd/ssh/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"context"
"os"
"time"

operationsv1alpha1 "github.com/gardener/gardener/pkg/apis/operations/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func SetBastionAvailabilityChecker(f func(hostname string, privateKey []byte) error) {
Expand Down Expand Up @@ -42,3 +45,7 @@ func SetKeepAliveInterval(d time.Duration) {

keepAliveInterval = d
}

func SetWaitForSignal(f func(ctx context.Context, o *SSHOptions, shootClient client.Client, bastion *operationsv1alpha1.Bastion, nodeHostname string, nodePrivateKeyFiles []string, signalChan <-chan struct{}) error) {
waitForSignal = f
}
196 changes: 98 additions & 98 deletions pkg/cmd/ssh/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,104 @@ var (

return cmd.Run()
}

// waitForSignal informs the user about their SSHOptions and keeps the
// bastion alive until gardenctl exits.
waitForSignal = func(ctx context.Context, o *SSHOptions, shootClient client.Client, bastion *operationsv1alpha1.Bastion, nodeHostname string, nodePrivateKeyFiles []string, signalChan <-chan struct{}) error {
if nodeHostname == "" {
nodeHostname = "IP_OR_HOSTNAME"

nodes, err := getNodes(ctx, shootClient)
if err != nil {
return fmt.Errorf("failed to list shoot cluster nodes: %w", err)
}

table := &metav1beta1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{
{
Name: "Node Name",
Type: "string",
Format: "name",
},
{
Name: "Status",
Type: "string",
},
{
Name: "IP",
Type: "string",
},
{
Name: "Hostname",
Type: "string",
},
},
Rows: []metav1.TableRow{},
}

for _, node := range nodes {
ip := ""
hostname := ""
status := "Ready"

if !isNodeReady(node) {
status = "Not Ready"
}

for _, addr := range node.Status.Addresses {
switch addr.Type {
case corev1.NodeInternalIP:
ip = addr.Address

case corev1.NodeInternalDNS:
hostname = addr.Address

// internal names have priority, as we jump via a bastion host,
// but in case the cloud provider does not offer internal IPs,
// we fallback to external values

case corev1.NodeExternalIP:
if ip == "" {
ip = addr.Address
}

case corev1.NodeExternalDNS:
if hostname == "" {
hostname = addr.Address
}
}
}

table.Rows = append(table.Rows, metav1.TableRow{
Cells: []interface{}{node.Name, status, ip, hostname},
})
}

fmt.Fprintln(o.IOStreams.Out, "The shoot cluster has the following nodes:")
fmt.Fprintln(o.IOStreams.Out, "")

printer := printers.NewTablePrinter(printers.PrintOptions{})
if err := printer.PrintObj(table, o.IOStreams.Out); err != nil {
return fmt.Errorf("failed to output node table: %w", err)
}

fmt.Fprintln(o.IOStreams.Out, "")
}

bastionAddr := preferredBastionAddress(bastion)
connectCmd := sshCommandLine(o, bastionAddr, nodePrivateKeyFiles, nodeHostname)

fmt.Fprintln(o.IOStreams.Out, "Connect to shoot nodes by using the bastion as a proxy/jump host, for example:")
fmt.Fprintln(o.IOStreams.Out, "")
fmt.Fprintln(o.IOStreams.Out, connectCmd)
fmt.Fprintln(o.IOStreams.Out, "")

fmt.Fprintln(o.IOStreams.Out, "Press Ctrl-C to stop gardenctl, after which the bastion will be removed.")

<-signalChan

return nil
}
)

// SSHOptions is a struct to support ssh command
Expand Down Expand Up @@ -841,104 +939,6 @@ func remoteShell(ctx context.Context, o *SSHOptions, bastion *operationsv1alpha1
return execCommand(ctx, "ssh", args, o)
}

// waitForSignal informs the user about their SSHOptions and keeps the
// bastion alive until gardenctl exits.
func waitForSignal(ctx context.Context, o *SSHOptions, shootClient client.Client, bastion *operationsv1alpha1.Bastion, nodeHostname string, nodePrivateKeyFiles []string, signalChan <-chan struct{}) error {
if nodeHostname == "" {
nodeHostname = "IP_OR_HOSTNAME"

nodes, err := getNodes(ctx, shootClient)
if err != nil {
return fmt.Errorf("failed to list shoot cluster nodes: %w", err)
}

table := &metav1beta1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{
{
Name: "Node Name",
Type: "string",
Format: "name",
},
{
Name: "Status",
Type: "string",
},
{
Name: "IP",
Type: "string",
},
{
Name: "Hostname",
Type: "string",
},
},
Rows: []metav1.TableRow{},
}

for _, node := range nodes {
ip := ""
hostname := ""
status := "Ready"

if !isNodeReady(node) {
status = "Not Ready"
}

for _, addr := range node.Status.Addresses {
switch addr.Type {
case corev1.NodeInternalIP:
ip = addr.Address

case corev1.NodeInternalDNS:
hostname = addr.Address

// internal names have priority, as we jump via a bastion host,
// but in case the cloud provider does not offer internal IPs,
// we fallback to external values

case corev1.NodeExternalIP:
if ip == "" {
ip = addr.Address
}

case corev1.NodeExternalDNS:
if hostname == "" {
hostname = addr.Address
}
}
}

table.Rows = append(table.Rows, metav1.TableRow{
Cells: []interface{}{node.Name, status, ip, hostname},
})
}

fmt.Fprintln(o.IOStreams.Out, "The shoot cluster has the following nodes:")
fmt.Fprintln(o.IOStreams.Out, "")

printer := printers.NewTablePrinter(printers.PrintOptions{})
if err := printer.PrintObj(table, o.IOStreams.Out); err != nil {
return fmt.Errorf("failed to output node table: %w", err)
}

fmt.Fprintln(o.IOStreams.Out, "")
}

bastionAddr := preferredBastionAddress(bastion)
connectCmd := sshCommandLine(o, bastionAddr, nodePrivateKeyFiles, nodeHostname)

fmt.Fprintln(o.IOStreams.Out, "Connect to shoot nodes by using the bastion as a proxy/jump host, for example:")
fmt.Fprintln(o.IOStreams.Out, "")
fmt.Fprintln(o.IOStreams.Out, connectCmd)
fmt.Fprintln(o.IOStreams.Out, "")

fmt.Fprintln(o.IOStreams.Out, "Press Ctrl-C to stop gardenctl, after which the bastion will be removed.")

<-signalChan

return nil
}

func isNodeReady(node corev1.Node) bool {
for _, cond := range node.Status.Conditions {
if cond.Type == corev1.NodeReady {
Expand Down
49 changes: 49 additions & 0 deletions pkg/cmd/ssh/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,33 @@ var _ = Describe("SSH Command", func() {

Expect(logs).To(ContainSubstring("Bastion is ready, skipping availability check"))
})

It("should not keep alive the bastion", func() {
options := ssh.NewSSHOptions(streams)
options.NoKeepalive = true
options.KeepBastion = true
options.Interactive = false

cmd := ssh.NewCmdSSH(factory, options)

ssh.SetWaitForSignal(func(ctx context.Context, o *ssh.SSHOptions, shootClient client.Client, bastion *operationsv1alpha1.Bastion, nodeHostname string, nodePrivateKeyFiles []string, signalChan <-chan struct{}) error {
err := errors.New("this function should not be executed as of NoKeepalive = true")
Fail(err.Error())
return err
})
ssh.SetExecCommand(func(ctx context.Context, command string, args []string, o *ssh.SSHOptions) error {
err := errors.New("this function should not be executed as of NoKeepalive = true")
Fail(err.Error())
return err
})

// simulate an external controller processing the bastion and proving a successful status
go waitForBastionThenSetBastionReady(ctx, gardenClient, bastionName, *testProject.Spec.Namespace, bastionHostname, bastionIP)

Expect(cmd.RunE(cmd, nil)).To(Succeed())

Expect(logs).To(ContainSubstring("Bastion host became available."))
})
})

Describe("ValidArgsFunction", func() {
Expand Down Expand Up @@ -565,6 +592,28 @@ var _ = Describe("SSH Options", func() {
Expect(o.Validate()).NotTo(Succeed())
})

Context("no-keepalive", func() {
It("should require non-interactive mode", func() {
o := ssh.NewSSHOptions(streams)
o.NoKeepalive = true
o.KeepBastion = true

o.Interactive = true

Expect(o.Validate()).NotTo(Succeed())
})

It("should require keep bastion", func() {
o := ssh.NewSSHOptions(streams)
o.NoKeepalive = true
o.Interactive = false

o.KeepBastion = false

Expect(o.Validate()).NotTo(Succeed())
})
})

It("should require a public SSH key file", func() {
o := ssh.NewSSHOptions(streams)
o.CIDRs = []string{"8.8.8.8/32"}
Expand Down

0 comments on commit 78dd0ff

Please sign in to comment.