mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Merge pull request #470 from captainroy-hy/vela-portforward
vela port-forward
This commit is contained in:
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user