portforward.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cmd
  14. import (
  15. "fmt"
  16. "io"
  17. "net/url"
  18. "os"
  19. "os/signal"
  20. "github.com/renstrom/dedent"
  21. "github.com/spf13/cobra"
  22. "k8s.io/kubernetes/pkg/api"
  23. "k8s.io/kubernetes/pkg/client/restclient"
  24. client "k8s.io/kubernetes/pkg/client/unversioned"
  25. "k8s.io/kubernetes/pkg/client/unversioned/portforward"
  26. "k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
  27. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  28. )
  29. // PortForwardOptions contains all the options for running the port-forward cli command.
  30. type PortForwardOptions struct {
  31. Namespace string
  32. PodName string
  33. Config *restclient.Config
  34. Client *client.Client
  35. Ports []string
  36. PortForwarder portForwarder
  37. StopChannel chan struct{}
  38. ReadyChannel chan struct{}
  39. }
  40. var (
  41. portforward_example = dedent.Dedent(`
  42. # Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod
  43. kubectl port-forward mypod 5000 6000
  44. # Listen on port 8888 locally, forwarding to 5000 in the pod
  45. kubectl port-forward mypod 8888:5000
  46. # Listen on a random port locally, forwarding to 5000 in the pod
  47. kubectl port-forward mypod :5000
  48. # Listen on a random port locally, forwarding to 5000 in the pod
  49. kubectl port-forward mypod 0:5000`)
  50. )
  51. func NewCmdPortForward(f *cmdutil.Factory, cmdOut, cmdErr io.Writer) *cobra.Command {
  52. opts := &PortForwardOptions{
  53. PortForwarder: &defaultPortForwarder{
  54. cmdOut: cmdOut,
  55. cmdErr: cmdErr,
  56. },
  57. }
  58. cmd := &cobra.Command{
  59. Use: "port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
  60. Short: "Forward one or more local ports to a pod",
  61. Long: "Forward one or more local ports to a pod.",
  62. Example: portforward_example,
  63. Run: func(cmd *cobra.Command, args []string) {
  64. if err := opts.Complete(f, cmd, args, cmdOut, cmdErr); err != nil {
  65. cmdutil.CheckErr(err)
  66. }
  67. if err := opts.Validate(); err != nil {
  68. cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
  69. }
  70. if err := opts.RunPortForward(); err != nil {
  71. cmdutil.CheckErr(err)
  72. }
  73. },
  74. }
  75. cmd.Flags().StringP("pod", "p", "", "Pod name")
  76. // TODO support UID
  77. return cmd
  78. }
  79. type portForwarder interface {
  80. ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error
  81. }
  82. type defaultPortForwarder struct {
  83. cmdOut, cmdErr io.Writer
  84. }
  85. func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
  86. dialer, err := remotecommand.NewExecutor(opts.Config, method, url)
  87. if err != nil {
  88. return err
  89. }
  90. fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.cmdOut, f.cmdErr)
  91. if err != nil {
  92. return err
  93. }
  94. return fw.ForwardPorts()
  95. }
  96. // Complete completes all the required options for port-forward cmd.
  97. func (o *PortForwardOptions) Complete(f *cmdutil.Factory, cmd *cobra.Command, args []string, cmdOut io.Writer, cmdErr io.Writer) error {
  98. var err error
  99. o.PodName = cmdutil.GetFlagString(cmd, "pod")
  100. if len(o.PodName) == 0 && len(args) == 0 {
  101. return cmdutil.UsageError(cmd, "POD is required for port-forward")
  102. }
  103. if len(o.PodName) != 0 {
  104. printDeprecationWarning("port-forward POD", "-p POD")
  105. o.Ports = args
  106. } else {
  107. o.PodName = args[0]
  108. o.Ports = args[1:]
  109. }
  110. o.Namespace, _, err = f.DefaultNamespace()
  111. if err != nil {
  112. return err
  113. }
  114. o.Client, err = f.Client()
  115. if err != nil {
  116. return err
  117. }
  118. o.Config, err = f.ClientConfig()
  119. if err != nil {
  120. return err
  121. }
  122. o.StopChannel = make(chan struct{}, 1)
  123. o.ReadyChannel = make(chan struct{})
  124. return nil
  125. }
  126. // Validate validates all the required options for port-forward cmd.
  127. func (o PortForwardOptions) Validate() error {
  128. if len(o.PodName) == 0 {
  129. return fmt.Errorf("pod name must be specified")
  130. }
  131. if len(o.Ports) < 1 {
  132. return fmt.Errorf("at least 1 PORT is required for port-forward")
  133. }
  134. if o.PortForwarder == nil || o.Client == nil || o.Config == nil {
  135. return fmt.Errorf("client, client config, and portforwarder must be provided")
  136. }
  137. return nil
  138. }
  139. // RunPortForward implements all the necessary functionality for port-forward cmd.
  140. func (o PortForwardOptions) RunPortForward() error {
  141. pod, err := o.Client.Pods(o.Namespace).Get(o.PodName)
  142. if err != nil {
  143. return err
  144. }
  145. if pod.Status.Phase != api.PodRunning {
  146. return fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
  147. }
  148. signals := make(chan os.Signal, 1)
  149. signal.Notify(signals, os.Interrupt)
  150. defer signal.Stop(signals)
  151. go func() {
  152. <-signals
  153. if o.StopChannel != nil {
  154. close(o.StopChannel)
  155. }
  156. }()
  157. req := o.Client.RESTClient.Post().
  158. Resource("pods").
  159. Namespace(o.Namespace).
  160. Name(pod.Name).
  161. SubResource("portforward")
  162. return o.PortForwarder.ForwardPorts("POST", req.URL(), o)
  163. }