Merge pull request #470 from captainroy-hy/vela-portforward

vela port-forward
This commit is contained in:
Jianbo Sun
2020-10-29 15:34:29 +08:00
committed by GitHub
8 changed files with 291 additions and 1 deletions
+2 -1
View File
@@ -30,6 +30,7 @@ vela [flags]
* [vela install](vela_install.md) - Initialize vela on both client and server
* [vela logs](vela_logs.md) - Tail logs for application
* [vela metric](vela_metric.md) - Attach metric trait to an app
* [vela port-forward](vela_port-forward.md) - Forward one or more local ports to a Pod of specified service
* [vela route](vela_route.md) - Attach route trait to an app
* [vela scaler](vela_scaler.md) - Attach scaler trait to an app
* [vela svc](vela_svc.md) - Manage services
@@ -40,4 +41,4 @@ vela [flags]
* [vela version](vela_version.md) - Prints out build version information
* [vela workloads](vela_workloads.md) - List workloads
###### Auto generated by spf13/cobra on 28-Oct-2020
###### Auto generated by spf13/cobra on 29-Oct-2020
+31
View File
@@ -0,0 +1,31 @@
## vela port-forward
Forward one or more local ports to a Pod of a service in an application
### Synopsis
Forward one or more local ports to a Pod of a service in an application
```
vela port-forward APP_NAME [options] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N] [flags]
```
### Options
```
--address strings Addresses to listen on (comma separated). Only accepts IP addresses or localhost as a value. When localhost is supplied, vela will try to bind on both 127.0.0.1 and ::1 and will fail if neither of these addresses are available to bind. (default [localhost])
-h, --help help for port-forward
--pod-running-timeout duration The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running (default 1m0s)
```
### Options inherited from parent commands
```
-e, --env string specify environment name for application
```
### SEE ALSO
* [vela](vela.md) -
###### Auto generated by spf13/cobra on 29-Oct-2020
+1
View File
@@ -29,6 +29,7 @@ var _ = ginkgo.Describe("Application", func() {
e2e.ApplicationStatusContext("app status", applicationName, workloadType)
e2e.ApplicationCompStatusContext("svc status", applicationName, workloadType, envName)
e2e.ApplicationExecContext("exec -- COMMAND", applicationName)
e2e.ApplicationPortForwardContext("port-forward", applicationName)
e2e.ApplicationInitIntercativeCliContext("init", appNameForInit, workloadType)
e2e.WorkloadDeleteContext("delete", applicationName)
e2e.WorkloadDeleteContext("delete", appNameForInit)
+10
View File
@@ -37,6 +37,16 @@ func Exec(cli string) (string, error) {
s := session.Wait(30 * time.Second)
return string(s.Out.Contents()) + string(s.Err.Contents()), nil
}
func ExecAndTerminate(cli string) (string, error) {
var output []byte
session, err := AsyncExec(cli)
if err != nil {
return string(output), err
}
time.Sleep(3 * time.Second)
s := session.Terminate()
return string(s.Out.Contents()) + string(s.Err.Contents()), nil
}
func LongTimeExec(cli string, timeout time.Duration) (string, error) {
var output []byte
+11
View File
@@ -241,6 +241,17 @@ var (
})
}
ApplicationPortForwardContext = func(context string, appName string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get output of portward successfully", func() {
cli := fmt.Sprintf("vela port-forward %s 8080:8080 ", appName)
output, err := ExecAndTerminate(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Forward successfully"))
})
})
}
ApplicationInitIntercativeCliContext = func(context string, appName string, workloadType string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should init app through interactive questions", func() {
+1
View File
@@ -87,6 +87,7 @@ func NewCommand() *cobra.Command {
NewDashboardCommand(commandArgs, ioStream, fake.FrontendSource),
NewExecCommand(commandArgs, ioStream),
NewPortForwardCommand(commandArgs, ioStream),
NewLogsCommand(commandArgs, ioStream),
NewTemplateCommand(commandArgs, ioStream),
+185
View File
@@ -0,0 +1,185 @@
package commands
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"github.com/oam-dev/kubevela/api/types"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"github.com/oam-dev/kubevela/pkg/application"
"github.com/oam-dev/kubevela/pkg/commands/util"
velacmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
cmdpf "k8s.io/kubectl/pkg/cmd/portforward"
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
)
type VelaPortForwardOptions struct {
Cmd *cobra.Command
Args []string
ioStreams velacmdutil.IOStreams
context.Context
VelaC types.Args
Env *types.EnvMeta
App *application.Application
f k8scmdutil.Factory
kcPortForwardOptions *cmdpf.PortForwardOptions
ClientSet kubernetes.Interface
}
func NewPortForwardCommand(c types.Args, ioStreams velacmdutil.IOStreams) *cobra.Command {
o := &VelaPortForwardOptions{
VelaC: c,
ioStreams: ioStreams,
kcPortForwardOptions: &cmdpf.PortForwardOptions{
PortForwarder: &defaultPortForwarder{ioStreams},
},
}
cmd := &cobra.Command{
Use: "port-forward APP_NAME [options] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
Short: "Forward one or more local ports to a Pod of a service in an application",
Long: "Forward one or more local ports to a Pod of a service in an application",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
ioStreams.Error("Please specify application name and list of ports.")
return nil
}
if err := o.Init(context.Background(), cmd, args); err != nil {
return err
}
if err := o.Complete(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}
return nil
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
}
cmd.Flags().StringSliceVar(&o.kcPortForwardOptions.Address, "address", []string{"localhost"}, "Addresses to listen on (comma separated). Only accepts IP addresses or localhost as a value. When localhost is supplied, vela will try to bind on both 127.0.0.1 and ::1 and will fail if neither of these addresses are available to bind.")
cmd.Flags().Duration(podRunningTimeoutFlag, defaultPodExecTimeout,
"The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running",
)
return cmd
}
func (o *VelaPortForwardOptions) Init(ctx context.Context, cmd *cobra.Command, argsIn []string) error {
o.Context = ctx
o.Cmd = cmd
o.Args = argsIn
env, err := GetEnv(o.Cmd)
if err != nil {
return err
}
o.Env = env
app, err := application.Load(env.Name, o.Args[0])
if err != nil {
return err
}
o.App = app
cf := genericclioptions.NewConfigFlags(true)
cf.Namespace = &o.Env.Namespace
o.f = k8scmdutil.NewFactory(k8scmdutil.NewMatchVersionFlags(cf))
if o.ClientSet == nil {
c, err := kubernetes.NewForConfig(o.VelaC.Config)
if err != nil {
return err
}
o.ClientSet = c
}
return nil
}
func (o *VelaPortForwardOptions) Complete() error {
svcName, err := util.AskToChooseOneService(o.App.GetComponents())
if err != nil {
return err
}
podName, err := o.getPodName(svcName)
if err != nil {
return err
}
args := make([]string, len(o.Args))
copy(args, o.Args)
args[0] = podName
return o.kcPortForwardOptions.Complete(o.f, o.Cmd, args)
}
func (o *VelaPortForwardOptions) getPodName(svcName string) (string, error) {
podList, err := o.ClientSet.CoreV1().Pods(o.Env.Namespace).List(o.Context, v1.ListOptions{
LabelSelector: labels.Set(map[string]string{
oam.LabelAppComponent: svcName,
}).String(),
})
if err != nil {
return "", err
}
if podList != nil && len(podList.Items) == 0 {
return "", fmt.Errorf("cannot get pods")
}
for _, p := range podList.Items {
if strings.HasPrefix(p.Name, svcName+"-") {
return p.Name, nil
}
}
return podList.Items[0].Name, nil
}
func (o *VelaPortForwardOptions) Run() error {
go func() {
<-o.kcPortForwardOptions.ReadyChannel
o.ioStreams.Info("\nForward successfully! Opening browser ...")
local, _ := splitPort(o.Args[1])
var url = "http://127.0.0.1:" + local
if err := OpenBrowser(url); err != nil {
o.ioStreams.Errorf("\nFailed to open browser: %v", err)
}
}()
return o.kcPortForwardOptions.RunPortForward()
}
func splitPort(port string) (local, remote string) {
parts := strings.Split(port, ":")
if len(parts) == 2 {
return parts[0], parts[1]
}
return parts[0], parts[0]
}
type defaultPortForwarder struct {
velacmdutil.IOStreams
}
func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts cmdpf.PortForwardOptions) error {
transport, upgrader, err := spdy.RoundTripperFor(opts.Config)
if err != nil {
return err
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
fw, err := portforward.NewOnAddresses(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut)
if err != nil {
return err
}
return fw.ForwardPorts()
}
+50
View File
@@ -0,0 +1,50 @@
package commands
import (
"context"
"os"
"testing"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"github.com/oam-dev/kubevela/api/types"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/kubectl/pkg/cmd/portforward"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
)
func TestPortForwardCommand(t *testing.T) {
fakePod := corev1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: "fakePod",
Namespace: "default",
ResourceVersion: "10",
Labels: map[string]string{
oam.LabelAppComponent: "fakeComp",
}},
}
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
io := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
fakeC := types.Args{
Config: tf.ClientConfigVal,
}
cmd := NewPortForwardCommand(fakeC, io)
cmd.PersistentFlags().StringP("env", "e", "", "")
fakeClientSet := k8sfake.NewSimpleClientset(&corev1.PodList{
Items: []corev1.Pod{fakePod},
})
o := &VelaPortForwardOptions{
ioStreams: io,
kcPortForwardOptions: &portforward.PortForwardOptions{},
f: tf,
ClientSet: fakeClientSet,
}
err := o.Init(context.Background(), cmd, []string{"fakeApp", "8081:8080"})
assert.NoError(t, err)
}