Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh - completion with machine objects #347

Merged
merged 16 commits into from
Jan 10, 2024
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/fatih/color v1.15.0
github.com/gardener/gardener v1.80.3
github.com/gardener/gardener-extension-provider-openstack v1.36.0
github.com/gardener/machine-controller-manager v0.50.0
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.1
github.com/mitchellh/go-homedir v1.1.0
Expand Down Expand Up @@ -45,7 +46,6 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gardener/etcd-druid v0.19.2 // indirect
github.com/gardener/hvpa-controller/api v0.5.0 // indirect
github.com/gardener/machine-controller-manager v0.48.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.2.4 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ github.com/gardener/gardener-extension-provider-openstack v1.36.0 h1:Zif2gepqRde
github.com/gardener/gardener-extension-provider-openstack v1.36.0/go.mod h1:fip83z/jRkH0kTyx0/GGTzSRgcL4hYVGi9i3x9ZROVc=
github.com/gardener/hvpa-controller/api v0.5.0 h1:f4F3O7YUrenwh4S3TgPREPiB287JjjUiUL18OqPLyAA=
github.com/gardener/hvpa-controller/api v0.5.0/go.mod h1:QQl3ELkCaki+8RhXl0FZMfvnm0WCGwGJlGmrxJj6lvM=
github.com/gardener/machine-controller-manager v0.48.1 h1:Oxr5e6gRm7P40Ds4nGlga/0nmfF7cH4rOfjthR6Mm38=
github.com/gardener/machine-controller-manager v0.48.1/go.mod h1:Axeu1Oh3agySk0oR4T+FUNax41Ni2K8tuksu8KRHuh0=
github.com/gardener/machine-controller-manager v0.50.0 h1:3dcQjzueFU1TGgprV00adjb3OCR99myTBx8DQGxywks=
github.com/gardener/machine-controller-manager v0.50.0/go.mod h1:RySZ40AgbNV/wMq60G/w49kb+okbj5Xs1A6usz5Pm/I=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
operationsv1alpha1 "github.com/gardener/gardener/pkg/apis/operations/v1alpha1"
seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1"
machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes/scheme"

Expand All @@ -19,6 +20,7 @@ func main() {
utilruntime.Must(gardencorev1beta1.AddToScheme(scheme.Scheme))
utilruntime.Must(operationsv1alpha1.AddToScheme(scheme.Scheme))
utilruntime.Must(seedmanagementv1alpha1.AddToScheme(scheme.Scheme))
utilruntime.Must(machinev1alpha1.AddToScheme(scheme.Scheme))

cmd.Execute()
}
103 changes: 76 additions & 27 deletions pkg/cmd/ssh/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/gardener/gardener/pkg/utils"
gutil "github.com/gardener/gardener/pkg/utils/gardener"
"github.com/gardener/gardener/pkg/utils/secrets"
machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/crypto/ssh"
Expand Down Expand Up @@ -489,20 +490,20 @@ func (o *SSHOptions) Run(f util.Factory) error {
ctx := f.Context()
logger := klog.FromContext(ctx)

// sshTarget is the target used for the run method
sshTarget, err := manager.CurrentTarget()
// currentTarget is the target used for the run method
currentTarget, err := manager.CurrentTarget()
if err != nil {
return err
}

// create client for the garden cluster
gardenClient, err := manager.GardenClient(sshTarget.GardenName())
gardenClient, err := manager.GardenClient(currentTarget.GardenName())
if err != nil {
return err
}

if sshTarget.ShootName() == "" && sshTarget.SeedName() != "" {
shoot, err := gardenClient.GetShootOfManagedSeed(ctx, sshTarget.SeedName())
if currentTarget.ShootName() == "" && currentTarget.SeedName() != "" {
shoot, err := gardenClient.GetShootOfManagedSeed(ctx, currentTarget.SeedName())
if err != nil {
if apierrors.IsNotFound(err) {
return fmt.Errorf("cannot ssh to non-managed seeds: %w", err)
Expand All @@ -516,28 +517,28 @@ func (o *SSHOptions) Run(f util.Factory) error {
Namespace: "garden",
Name: shoot.Name,
},
"seed", sshTarget.SeedName())
"seed", currentTarget.SeedName())

sshTarget = sshTarget.WithProjectName("garden").WithShootName(shoot.Name)
currentTarget = currentTarget.WithProjectName("garden").WithShootName(shoot.Name)
}

if sshTarget.ShootName() == "" {
if currentTarget.ShootName() == "" {
return target.ErrNoShootTargeted
}

printTargetInformation(logger, sshTarget)
printTargetInformation(logger, currentTarget)

// fetch targeted shoot (ctx is cancellable to stop the keep alive goroutine later)
ctx, cancel := context.WithCancel(ctx)
defer cancel()

shoot, err := gardenClient.FindShoot(ctx, sshTarget.AsListOption())
shoot, err := gardenClient.FindShoot(ctx, currentTarget.AsListOption())
if err != nil {
return err
}

// check access restrictions
ok, err := o.checkAccessRestrictions(manager.Configuration(), sshTarget.GardenName(), f.TargetFlags(), shoot)
ok, err := o.checkAccessRestrictions(manager.Configuration(), currentTarget.GardenName(), f.TargetFlags(), shoot)
if err != nil {
return err
} else if !ok {
Expand Down Expand Up @@ -567,7 +568,7 @@ func (o *SSHOptions) Run(f util.Factory) error {
nodePrivateKeyFiles = append(nodePrivateKeyFiles, PrivateKeyFile(filename))
}

shootClient, err := manager.ShootClient(ctx, sshTarget)
shootClient, err := manager.ShootClient(ctx, currentTarget)
if err != nil {
return err
}
Expand Down Expand Up @@ -820,13 +821,9 @@ func cleanup(ctx context.Context, o *SSHOptions, gardenClient client.Client, bas
}
}

func getNodeNamesFromShoot(f util.Factory, prefix string) ([]string, error) {
manager, err := f.Manager()
if err != nil {
return nil, err
}
func getNodeNamesFromMachinesOrNodes(ctx context.Context, manager target.Manager) ([]string, error) {
logger := klog.FromContext(ctx)

// validate the current target
currentTarget, err := manager.CurrentTarget()
if err != nil {
return nil, err
Expand All @@ -836,25 +833,68 @@ func getNodeNamesFromShoot(f util.Factory, prefix string) ([]string, error) {
return nil, errors.New("no Shoot cluster targeted")
}

// create client for the shoot cluster
shootClient, err := manager.ShootClient(f.Context(), currentTarget)
nodeNames, err := getNodeNamesFromMachines(ctx, manager, currentTarget)
if err != nil {
if !apierrors.IsForbidden(err) {
logger.Info("failed to fetch node names from machine objects", "err", err)
}

return getNodeNamesFromNodes(ctx, manager, currentTarget)
}

return nodeNames, nil
}

func getNodeNamesFromMachines(ctx context.Context, manager target.Manager, currentTarget target.Target) ([]string, error) {
gardenClient, err := manager.GardenClient(currentTarget.GardenName())
if err != nil {
return nil, fmt.Errorf("failed to create garden cluster client: %w", err)
}

shoot, err := gardenClient.FindShoot(ctx, currentTarget.AsListOption())
if err != nil {
return nil, err
}

// fetch all nodes
nodes, err := getNodes(f.Context(), shootClient)
seedTarget := target.NewTarget(currentTarget.GardenName(), "", *shoot.Spec.SeedName, "")

seedClient, err := manager.SeedClient(ctx, seedTarget)
if err != nil {
return nil, err
}

// collect names, filter by prefix
nodeNames := []string{}
machines, err := getMachines(ctx, seedClient, shoot.Status.TechnicalID)
if err != nil {
return nil, err
}

for _, node := range nodes {
if strings.HasPrefix(node.Name, prefix) {
nodeNames = append(nodeNames, node.Name)
var nodeNames []string

for _, machine := range machines {
if _, ok := machine.Labels[machinev1alpha1.NodeLabelKey]; !ok {
continue
}

nodeNames = append(nodeNames, machine.Labels[machinev1alpha1.NodeLabelKey])
}

return nodeNames, nil
}

func getNodeNamesFromNodes(ctx context.Context, manager target.Manager, currentTarget target.Target) ([]string, error) {
shootClient, err := manager.ShootClient(ctx, currentTarget)
if err != nil {
return nil, err
}

nodes, err := getNodes(ctx, shootClient)
if err != nil {
return nil, err
}

var nodeNames []string
for _, node := range nodes {
nodeNames = append(nodeNames, node.Name)
}

return nodeNames, nil
Expand Down Expand Up @@ -1103,6 +1143,15 @@ func getNodes(ctx context.Context, c client.Client) ([]corev1.Node, error) {
return nodeList.Items, nil
}

func getMachines(ctx context.Context, c client.Client, namespace string) ([]machinev1alpha1.Machine, error) {
machineList := machinev1alpha1.MachineList{}
if err := c.List(ctx, &machineList, client.InNamespace(namespace)); err != nil {
return nil, fmt.Errorf("failed to list machines: %w", err)
}

return machineList.Items, nil
}

func (o *SSHOptions) checkAccessRestrictions(cfg *config.Config, gardenName string, tf target.TargetFlags, shoot *gardencorev1beta1.Shoot) (bool, error) {
if cfg == nil {
return false, errors.New("garden configuration is required")
Expand Down
19 changes: 17 additions & 2 deletions pkg/cmd/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package ssh

import (
"strings"

"github.com/spf13/cobra"
"k8s.io/klog/v2"

Expand Down Expand Up @@ -51,13 +53,26 @@ gardenctl ssh --keep-bastion --bastion-name cli-xxxxxxxx --public-key-file /path
return nil, cobra.ShellCompDirectiveNoFileComp
}

nodeNames, err := getNodeNamesFromShoot(f, toComplete)
manager, err := f.Manager()
if err != nil {
logger.Error(err, "could not get manager from factory")
return nil, cobra.ShellCompDirectiveNoFileComp
}

nodeNames, err := getNodeNamesFromMachinesOrNodes(ctx, manager)
if err != nil {
logger.Error(err, "could not get node names from shoot")
return nil, cobra.ShellCompDirectiveNoFileComp
}

return nodeNames, cobra.ShellCompDirectiveNoFileComp
var completions []string
for _, nodeName := range nodeNames {
if strings.HasPrefix(nodeName, toComplete) {
completions = append(completions, nodeName)
}
}

return completions, cobra.ShellCompDirectiveNoFileComp
},
RunE: base.WrapRunE(o, f),
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/ssh/ssh_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
operationsv1alpha1 "github.com/gardener/gardener/pkg/apis/operations/v1alpha1"
machinev1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand All @@ -23,6 +24,7 @@ import (
func init() {
utilruntime.Must(gardencorev1beta1.AddToScheme(scheme.Scheme))
utilruntime.Must(operationsv1alpha1.AddToScheme(scheme.Scheme))
utilruntime.Must(machinev1alpha1.AddToScheme(scheme.Scheme))
}

func TestCommand(t *testing.T) {
Expand Down
Loading