@@ -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
9898func 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+
347482type usageError string
348483
349484func (e usageError ) Error () string {
0 commit comments