Skip to content

Commit c438fa7

Browse files
authored
Merge pull request #16 from segmentio/erikdw/support-looking-up-pod-names
support lookups for pod names
2 parents da532f8 + 0e7a458 commit c438fa7

File tree

6 files changed

+547
-745
lines changed

6 files changed

+547
-745
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@
1616

1717
# Emacs
1818
*~
19+
20+
# IDE directories
21+
.vscode
22+
.idea
23+
24+
# vim swap
25+
[._]*.sw[a-p]
26+
[._]sw[a-p]
27+
28+
# vim temporary
29+
*~

curl.go

Lines changed: 143 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,14 @@ func main() {
9090
defer stop()
9191

9292
if err := run(ctx); err != nil {
93-
fmt.Fprintf(os.Stderr, "* ERROR: %s\n", err)
93+
_, _ = fmt.Fprintf(os.Stderr, "* ERROR: %s\n", err)
9494
os.Exit(1)
9595
}
9696
}
9797

9898
func run(ctx context.Context) error {
9999
cArgs := make([]string, 0)
100-
flags.ParseAll(os.Args[1:], func(flag *pflag.Flag, value string) error {
100+
_ = flags.ParseAll(os.Args[1:], func(flag *pflag.Flag, value string) error {
101101
if flag.Name == "silent" {
102102
return nil // --silent is added later to all curl arguments so don't add here
103103
}
@@ -153,12 +153,7 @@ func run(ctx context.Context) error {
153153
return fmt.Errorf("malformed URL: %w", err)
154154
}
155155

156-
podName, podPort, err := net.SplitHostPort(requestURL.Host)
157-
if err != nil {
158-
podName = requestURL.Host
159-
podPort = ""
160-
}
161-
156+
// Initialize kube config and client before parsing host/port/resource
162157
kubeConfig := config.ToRawKubeConfigLoader()
163158
namespace, _, err := kubeConfig.Namespace()
164159
if err != nil {
@@ -173,6 +168,81 @@ func run(ctx context.Context) error {
173168
return err
174169
}
175170

171+
// Parse host and port, support <type>/<name>[:port] in host or host as type and first path segment as name
172+
hostPort := requestURL.Host
173+
var podName, podPort string
174+
var resourceType, resourceName string
175+
isResource := false
176+
// Check if host is a resource type or abbreviation
177+
if canonicalType, ok := resourceTypeMap[strings.ToLower(hostPort)]; ok && requestURL.Path != "" {
178+
// host is resource type, first path segment is resource name (and maybe :port)
179+
segments := strings.SplitN(strings.TrimLeft(requestURL.Path, "/"), "/", 2)
180+
resourceAndMaybePort := segments[0]
181+
resourceType = canonicalType
182+
if colonIdx := strings.LastIndex(resourceAndMaybePort, ":"); colonIdx > -1 {
183+
resourceName = resourceAndMaybePort[:colonIdx]
184+
podPort = resourceAndMaybePort[colonIdx+1:]
185+
} else {
186+
resourceName = resourceAndMaybePort
187+
}
188+
isResource = true
189+
// Remove the resource segment from the path for the actual HTTP request
190+
if len(segments) > 1 {
191+
requestURL.Path = "/" + segments[1]
192+
} else {
193+
requestURL.Path = "/"
194+
}
195+
if debug {
196+
_, _ = fmt.Fprintf(os.Stderr, "DEBUG: resourceType(from host)=%q, resourceName=%q, podPort=%q, newPath=%q\n", resourceType, resourceName, podPort, requestURL.Path)
197+
}
198+
} else if idx := strings.Index(hostPort, "/"); idx >= 0 {
199+
// Looks like <type>/<name>[:port] in host
200+
resourceAndMaybePort := hostPort
201+
resource := resourceAndMaybePort
202+
if colonIdx := strings.LastIndex(resourceAndMaybePort, ":"); colonIdx > -1 && colonIdx > idx {
203+
resource = resourceAndMaybePort[:colonIdx]
204+
podPort = resourceAndMaybePort[colonIdx+1:]
205+
}
206+
resourceParts := strings.SplitN(resource, "/", 2)
207+
if len(resourceParts) == 2 {
208+
resourceType, resourceName = resourceParts[0], resourceParts[1]
209+
isResource = true
210+
if debug {
211+
_, _ = fmt.Fprintf(os.Stderr, "DEBUG: resourceType=%q, resourceName=%q, podPort=%q\n", resourceType, resourceName, podPort)
212+
}
213+
} else {
214+
return fmt.Errorf("invalid resource format: %s", resource)
215+
}
216+
} else {
217+
// podname[:port]
218+
var err error
219+
podName, podPort, err = net.SplitHostPort(hostPort)
220+
if err != nil {
221+
podName = hostPort
222+
podPort = ""
223+
}
224+
if debug {
225+
_, _ = fmt.Fprintf(os.Stderr, "DEBUG: podName=%q, podPort=%q\n", podName, podPort)
226+
}
227+
}
228+
229+
if isResource {
230+
if debug || isVerbose(cArgs) {
231+
_, _ = fmt.Fprintf(os.Stderr, "Resolving resource: type=%s, name=%s, port=%s\n", resourceType, resourceName, podPort)
232+
}
233+
pods, resolvedPodName, err := resolvePodFromResource(ctx, client, namespace, resourceType, resourceName)
234+
if err != nil {
235+
return err
236+
}
237+
if debug || isVerbose(cArgs) {
238+
_, _ = fmt.Fprintf(os.Stderr, "Found %d pods, using pod/%s\n", len(pods), resolvedPodName)
239+
}
240+
podName = resolvedPodName
241+
if debug {
242+
_, _ = fmt.Fprintf(os.Stderr, "DEBUG: podName set to %q from resource\n", podName)
243+
}
244+
}
245+
176246
log.Printf("kubectl get -n %s pod/%s", namespace, podName)
177247
pod, err := client.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
178248
if err != nil {
@@ -344,6 +414,71 @@ func openPortForwarder(ctx context.Context, fwd portForwarderConfig) (*portforwa
344414
return portforward.New(dialer, ports, ctx.Done(), make(chan struct{}), fwd.stdout, fwd.stderr)
345415
}
346416

417+
// resourceTypeMap maps supported resource types and their abbreviations to canonical names
418+
var resourceTypeMap = map[string]string{
419+
"deployment": "deployment",
420+
"deploy": "deployment",
421+
"deployments": "deployment",
422+
"ds": "daemonset",
423+
"daemonset": "daemonset",
424+
"daemonsets": "daemonset",
425+
"sts": "statefulset",
426+
"statefulset": "statefulset",
427+
"statefulsets": "statefulset",
428+
}
429+
430+
// resolvePodFromResource finds a pod name for a given resource type and name in a namespace.
431+
func resolvePodFromResource(ctx context.Context, client *kubernetes.Clientset, namespace, resourceType, resourceName string) ([]corev1.Pod, string, error) {
432+
canonicalType, ok := resourceTypeMap[strings.ToLower(resourceType)]
433+
if !ok {
434+
return nil, "", fmt.Errorf("unsupported resource type: %s", resourceType)
435+
}
436+
var labelSelector string
437+
var err error
438+
439+
switch canonicalType {
440+
case "deployment":
441+
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, resourceName, metav1.GetOptions{})
442+
if err != nil {
443+
return nil, "", fmt.Errorf("failed to get deployment %s: %w", resourceName, err)
444+
}
445+
labelSelector = metav1.FormatLabelSelector(deployment.Spec.Selector)
446+
case "daemonset":
447+
daemonset, err := client.AppsV1().DaemonSets(namespace).Get(ctx, resourceName, metav1.GetOptions{})
448+
if err != nil {
449+
return nil, "", fmt.Errorf("failed to get daemonset %s: %w", resourceName, err)
450+
}
451+
labelSelector = metav1.FormatLabelSelector(daemonset.Spec.Selector)
452+
case "statefulset":
453+
sts, err := client.AppsV1().StatefulSets(namespace).Get(ctx, resourceName, metav1.GetOptions{})
454+
if err != nil {
455+
return nil, "", fmt.Errorf("failed to get statefulset %s: %w", resourceName, err)
456+
}
457+
labelSelector = metav1.FormatLabelSelector(sts.Spec.Selector)
458+
}
459+
460+
podsList, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
461+
LabelSelector: labelSelector,
462+
})
463+
if err != nil {
464+
return nil, "", fmt.Errorf("failed to list pods for %s %s: %w", canonicalType, resourceName, err)
465+
}
466+
if len(podsList.Items) == 0 {
467+
return nil, "", fmt.Errorf("no pods found for %s %s", canonicalType, resourceName)
468+
}
469+
return podsList.Items, podsList.Items[0].Name, nil
470+
}
471+
472+
// isVerbose checks if -v or --verbose is present in curl args
473+
func isVerbose(args []string) bool {
474+
for _, arg := range args {
475+
if arg == "-v" || arg == "--verbose" {
476+
return true
477+
}
478+
}
479+
return false
480+
}
481+
347482
type usageError string
348483

349484
func (e usageError) Error() string {

curl/parse.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package curl
2+
3+
import (
4+
"net/url"
5+
"strings"
6+
)
7+
8+
type ResourceTarget struct {
9+
IsResource bool
10+
ResourceType string
11+
ResourceName string
12+
PodName string
13+
PodPort string
14+
NewPath string
15+
}
16+
17+
// ParseResourceTarget parses the URL and returns resource/pod targeting info.
18+
func ParseResourceTarget(requestURL *url.URL, resourceTypeMap map[string]string) ResourceTarget {
19+
hostPort := requestURL.Host
20+
var podName, podPort string
21+
var resourceType, resourceName string
22+
isResource := false
23+
newPath := requestURL.Path
24+
25+
if canonicalType, ok := resourceTypeMap[strings.ToLower(hostPort)]; ok && requestURL.Path != "" {
26+
segments := strings.SplitN(strings.TrimLeft(requestURL.Path, "/"), "/", 2)
27+
resourceAndMaybePort := segments[0]
28+
resourceType = canonicalType
29+
if colonIdx := strings.LastIndex(resourceAndMaybePort, ":"); colonIdx > -1 {
30+
resourceName = resourceAndMaybePort[:colonIdx]
31+
podPort = resourceAndMaybePort[colonIdx+1:]
32+
} else {
33+
resourceName = resourceAndMaybePort
34+
}
35+
isResource = true
36+
if len(segments) > 1 {
37+
newPath = "/" + segments[1]
38+
} else {
39+
newPath = "/"
40+
}
41+
} else if idx := strings.Index(hostPort, "/"); idx >= 0 {
42+
resourceAndMaybePort := hostPort
43+
resource := resourceAndMaybePort
44+
if colonIdx := strings.LastIndex(resourceAndMaybePort, ":"); colonIdx > -1 && colonIdx > idx {
45+
resource = resourceAndMaybePort[:colonIdx]
46+
podPort = resourceAndMaybePort[colonIdx+1:]
47+
}
48+
resourceParts := strings.SplitN(resource, "/", 2)
49+
if len(resourceParts) == 2 {
50+
resourceType, resourceName = resourceParts[0], resourceParts[1]
51+
isResource = true
52+
}
53+
} else {
54+
// podname[:port]
55+
parts := strings.SplitN(hostPort, ":", 2)
56+
podName = parts[0]
57+
if len(parts) == 2 {
58+
podPort = parts[1]
59+
} else {
60+
podPort = ""
61+
}
62+
}
63+
64+
return ResourceTarget{
65+
IsResource: isResource,
66+
ResourceType: resourceType,
67+
ResourceName: resourceName,
68+
PodName: podName,
69+
PodPort: podPort,
70+
NewPath: newPath,
71+
}
72+
}

0 commit comments

Comments
 (0)