Skip to content

Commit 479a66e

Browse files
committed
Add eks port forwarding
1 parent a40b454 commit 479a66e

File tree

6 files changed

+232
-9
lines changed

6 files changed

+232
-9
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ Usage:
255255
256256
Available Commands:
257257
exec Execute a command in a container
258+
fwd Port forwarding
258259
get-token Get a token for authentication with an Amazon EKS cluster
259260
update-kubeconfig Configures kubectl so that you can connect to an Amazon EKS cluster
260261
@@ -283,12 +284,38 @@ gotoaws eks exec --cluster gotoaws --role cluster-admin -- cat /etc/passwd
283284
gotoaws eks exec --cluster gotoaws --role cluster-admin --namespace default --pod nginx -- date
284285
285286
Flags:
286-
--cluster string arn or name of the cluster (required)
287+
--cluster string arn or name of the cluster
287288
-c, --container string name of the container
288289
-h, --help help for exec
289290
-n, --namespace string namespace of the pod (default "all namespaces"
290291
-p, --pod string name of the pod
291-
-r, --role string arn or name of the role
292+
--role string arn or name of the role
293+
294+
Global Flags:
295+
--config string config file (default "$HOME/.config/configstore/gotoaws.json")
296+
--profile string AWS profile
297+
--region string AWS region
298+
--silent run gotoaws without printing logs
299+
--timeout duration timeout for network requests (default 15s)
300+
```
301+
302+
### Port forwarding
303+
```
304+
Usage:
305+
gotoaws eks fwd [flags]
306+
307+
Examples:
308+
gotoaws eks fwd --cluster gotoaws --role cluster-admin --pod nginx
309+
gotoaws eks fwd --cluster gotoaws --role cluster-admin --pod nginx --local 8000 --remote 80
310+
311+
Flags:
312+
--cluster string arn or name of the cluster
313+
-h, --help help for fwd
314+
-l, --local int32 the local port
315+
-n, --namespace string namespace of the pod (default "all namespaces"
316+
-p, --pod string name of the pod
317+
-r, --remote int32 the container port
318+
--role string arn or name of the role
292319
293320
Global Flags:
294321
--config string config file (default "$HOME/.config/configstore/gotoaws.json")

cmd/eks/eks.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/hupe1980/gotoaws/internal"
78
"github.com/hupe1980/gotoaws/pkg/config"
89
"github.com/hupe1980/gotoaws/pkg/eks"
910
"github.com/manifoldco/promptui"
@@ -21,6 +22,7 @@ func NewEKSCmd() *cobra.Command {
2122
newUpdateKubeconfigCmd(),
2223
newGetTokenCmd(),
2324
newExecCmd(),
25+
newFwdCmd(),
2426
)
2527

2628
return cmd
@@ -80,6 +82,12 @@ func findPod(cfg *config.Config, cluster *eks.Cluster, role, namespace, podName,
8082
}
8183

8284
if podName != "" {
85+
if namespace == "" {
86+
internal.PrintInfo("No namspespace was specified. Set namespace to \"default\"")
87+
88+
namespace = "default"
89+
}
90+
8391
var pods []eks.Pod
8492

8593
pods, err = finder.FindByIdentifier(namespace, podName, container)

cmd/eks/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ gotoaws eks exec --cluster gotoaws --role cluster-admin --namespace default --po
6161
}
6262

6363
cmd.Flags().StringVarP(&opts.clusterName, "cluster", "", "", "arn or name of the cluster")
64-
cmd.Flags().StringVarP(&opts.role, "role", "r", "", "arn or name of the role")
64+
cmd.Flags().StringVarP(&opts.role, "role", "", "", "arn or name of the role")
6565
cmd.Flags().StringVarP(&opts.namespace, "namespace", "n", "", "namespace of the pod (default \"all namespaces\"")
6666
cmd.Flags().StringVarP(&opts.pod, "pod", "p", "", "name of the pod")
6767
cmd.Flags().StringVarP(&opts.container, "container", "c", "", "name of the container")

cmd/eks/fwd.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package eks
2+
3+
import (
4+
"errors"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
"github.com/hupe1980/gotoaws/internal"
10+
"github.com/hupe1980/gotoaws/pkg/eks"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
type fwdOptions struct {
15+
clusterName string
16+
role string
17+
namespace string
18+
pod string
19+
container string
20+
remotePort int32
21+
localPort int32
22+
}
23+
24+
func newFwdCmd() *cobra.Command {
25+
opts := &fwdOptions{}
26+
cmd := &cobra.Command{
27+
Use: "fwd",
28+
Short: "Port forwarding",
29+
SilenceUsage: true,
30+
SilenceErrors: true,
31+
Example: `gotoaws eks fwd --cluster gotoaws --role cluster-admin --pod nginx
32+
gotoaws eks fwd --cluster gotoaws --role cluster-admin --pod nginx --local 8000 --remote 80`,
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
cfg, err := internal.NewConfigFromFlags()
35+
if err != nil {
36+
return err
37+
}
38+
39+
cluster, err := findCluster(cfg, opts.clusterName)
40+
if err != nil {
41+
return err
42+
}
43+
44+
client, err := eks.NewKubeclient(cfg, cluster, opts.role)
45+
if err != nil {
46+
return err
47+
}
48+
49+
pod, err := findPod(cfg, cluster, opts.role, opts.namespace, opts.pod, opts.container)
50+
if err != nil {
51+
return err
52+
}
53+
54+
containerPort := opts.remotePort
55+
if containerPort == 0 {
56+
if len(pod.ContainerPorts) > 0 {
57+
containerPort = pod.ContainerPorts[0].Port
58+
} else {
59+
return errors.New("container port cannot be determined")
60+
}
61+
}
62+
63+
stopCh := make(chan struct{}, 1)
64+
readyCh := make(chan struct{})
65+
66+
go func() {
67+
<-readyCh
68+
internal.PrintInfo("Port forwarding is ready")
69+
}()
70+
71+
sigs := make(chan os.Signal, 1)
72+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
73+
go func() {
74+
<-sigs
75+
if stopCh != nil {
76+
close(stopCh)
77+
}
78+
}()
79+
80+
return client.RunPortForward(&eks.PortForwardInput{
81+
Namespace: pod.Namespace,
82+
PodName: pod.Name,
83+
LocalPort: opts.localPort,
84+
ContainerPort: containerPort,
85+
StopCh: stopCh,
86+
ReadyCh: readyCh,
87+
})
88+
},
89+
}
90+
91+
cmd.Flags().StringVarP(&opts.clusterName, "cluster", "", "", "arn or name of the cluster")
92+
cmd.Flags().StringVarP(&opts.role, "role", "", "", "arn or name of the role")
93+
cmd.Flags().StringVarP(&opts.namespace, "namespace", "n", "", "namespace of the pod (default \"all namespaces\"")
94+
cmd.Flags().StringVarP(&opts.pod, "pod", "p", "", "name of the pod")
95+
cmd.Flags().Int32VarP(&opts.remotePort, "remote", "r", 0, "the container port")
96+
cmd.Flags().Int32VarP(&opts.localPort, "local", "l", 0, "the local port")
97+
98+
return cmd
99+
}

pkg/eks/pod.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import (
66
"github.com/hupe1980/gotoaws/pkg/config"
77
)
88

9+
// ContainerPort represents a network port in a single container.
10+
type ContainerPort struct {
11+
// Number of port to expose on the pod's IP address
12+
Port int32
13+
14+
// Protocol for port. Must be UDP, TCP, or SCTP.
15+
Protocol string
16+
}
17+
918
// An object representing a pod.
1019
type Pod struct {
1120
// The name of the pod.
@@ -16,6 +25,9 @@ type Pod struct {
1625

1726
// The name of the container
1827
Container string
28+
29+
// List of ports to expose from the container
30+
ContainerPorts []ContainerPort
1931
}
2032

2133
type PodFinder interface {
@@ -48,10 +60,22 @@ func (p *podFinder) Find(namespace, labelSelector string) ([]Pod, error) {
4860

4961
for _, p := range podList.Items {
5062
for _, c := range p.Spec.Containers {
63+
ports := []ContainerPort{}
64+
65+
for _, p := range c.Ports {
66+
protocol := "TCP"
67+
if p.Protocol != "" {
68+
protocol = string(p.Protocol)
69+
}
70+
71+
ports = append(ports, ContainerPort{Port: p.ContainerPort, Protocol: protocol})
72+
}
73+
5174
pods = append(pods, Pod{
52-
Name: p.Name,
53-
Namespace: p.Namespace,
54-
Container: c.Name,
75+
Name: p.Name,
76+
Namespace: p.Namespace,
77+
Container: c.Name,
78+
ContainerPorts: ports,
5579
})
5680
}
5781
}
@@ -73,10 +97,22 @@ func (p *podFinder) FindByIdentifier(namespace, podName, container string) ([]Po
7397

7498
for _, c := range pod.Spec.Containers {
7599
if container == "" || container == c.Name {
100+
ports := []ContainerPort{}
101+
102+
for _, p := range c.Ports {
103+
protocol := "TCP"
104+
if p.Protocol != "" {
105+
protocol = string(p.Protocol)
106+
}
107+
108+
ports = append(ports, ContainerPort{Port: p.ContainerPort, Protocol: protocol})
109+
}
110+
76111
pods = append(pods, Pod{
77-
Name: pod.Name,
78-
Namespace: pod.Namespace,
79-
Container: c.Name,
112+
Name: pod.Name,
113+
Namespace: pod.Namespace,
114+
Container: c.Name,
115+
ContainerPorts: ports,
80116
})
81117
}
82118
}

pkg/eks/portforward.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package eks
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"os"
7+
8+
"k8s.io/client-go/tools/portforward"
9+
"k8s.io/client-go/transport/spdy"
10+
)
11+
12+
type PortForwardInput struct {
13+
// Namespace of the pod
14+
Namespace string
15+
16+
// Name of the pod
17+
PodName string
18+
19+
// LocalPort is the local port that will be selected to expose the PodPort
20+
LocalPort int32
21+
22+
// ContainerPort is the target port for the pod
23+
ContainerPort int32
24+
25+
// StopCh is the channel used to manage the port forward lifecycle
26+
StopCh <-chan struct{}
27+
28+
// ReadyCh communicates when the tunnel is ready to receive traffic
29+
ReadyCh chan struct{}
30+
}
31+
32+
func (k *Kubeclient) RunPortForward(input *PortForwardInput) error {
33+
req := k.clientset.CoreV1().RESTClient().
34+
Post().
35+
Resource("pods").
36+
Namespace(input.Namespace).
37+
Name(input.PodName).
38+
SubResource("portforward")
39+
40+
transport, upgrader, err := spdy.RoundTripperFor(k.restCfg)
41+
if err != nil {
42+
return err
43+
}
44+
45+
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, http.MethodPost, req.URL())
46+
47+
fw, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", input.LocalPort, input.ContainerPort)}, input.StopCh, input.ReadyCh, os.Stdout, os.Stderr)
48+
if err != nil {
49+
return err
50+
}
51+
52+
return fw.ForwardPorts()
53+
}

0 commit comments

Comments
 (0)