Files
kubevela/pkg/commands/exec.go
roy wang 93ae8a9099 fix staticcheck issues
add staticcheck CI action

add staticcheck in Makefile

Signed-off-by: roywang <seiwy2010@gmail.com>
2021-01-29 18:42:03 +09:00

206 lines
5.3 KiB
Go

package commands
import (
"context"
"fmt"
"strings"
"time"
"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"
cmdexec "k8s.io/kubectl/pkg/cmd/exec"
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/appfile/api"
"github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/oam"
)
const (
podRunningTimeoutFlag = "pod-running-timeout"
defaultPodExecTimeout = 60 * time.Second
defaultStdin = true
defaultTTY = true
)
// VelaExecOptions creates options for `exec` command
type VelaExecOptions struct {
Cmd *cobra.Command
Args []string
Stdin bool
TTY bool
ServiceName string
context.Context
VelaC types.Args
Env *types.EnvMeta
App *api.Application
f k8scmdutil.Factory
kcExecOptions *cmdexec.ExecOptions
ClientSet kubernetes.Interface
}
// NewExecCommand creates `exec` command
func NewExecCommand(c types.Args, ioStreams util.IOStreams) *cobra.Command {
o := &VelaExecOptions{
kcExecOptions: &cmdexec.ExecOptions{
StreamOptions: cmdexec.StreamOptions{
IOStreams: genericclioptions.IOStreams{
In: ioStreams.In,
Out: ioStreams.Out,
ErrOut: ioStreams.ErrOut,
},
},
Executor: &cmdexec.DefaultRemoteExecutor{},
},
}
cmd := &cobra.Command{
Use: "exec [flags] APP_NAME -- COMMAND [args...]",
Short: "Execute command in a container",
Long: "Execute command in a container",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if err := c.SetConfig(); err != nil {
return err
}
o.VelaC = c
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
ioStreams.Error("Please specify an application name.")
return nil
}
argsLenAtDash := cmd.ArgsLenAtDash()
if argsLenAtDash != 1 {
ioStreams.Error("Please specify at least one command for the container.")
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().BoolVarP(&o.Stdin, "stdin", "i", defaultStdin, "Pass stdin to the container")
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", defaultTTY, "Stdin is a TTY")
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",
)
cmd.Flags().StringVarP(&o.ServiceName, "svc", "s", "", "service name")
return cmd
}
// Init prepares the arguments accepted by the Exec command
func (o *VelaExecOptions) Init(ctx context.Context, c *cobra.Command, argsIn []string) error {
o.Context = ctx
o.Cmd = c
o.Args = argsIn
env, err := GetEnv(o.Cmd)
if err != nil {
return err
}
o.Env = env
app, err := appfile.LoadApplication(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
}
// Complete loads data from the command environment
func (o *VelaExecOptions) Complete() error {
compName, err := o.getComponentName()
if err != nil {
return err
}
podName, err := o.getPodName(compName)
if err != nil {
return err
}
o.kcExecOptions.StreamOptions.Stdin = o.Stdin
o.kcExecOptions.StreamOptions.TTY = o.TTY
args := make([]string, len(o.Args))
copy(args, o.Args)
// args for kcExecOptions MUST be in such formart:
// [podName, COMMAND...]
args[0] = podName
return o.kcExecOptions.Complete(o.f, o.Cmd, args, 1)
}
func (o *VelaExecOptions) getComponentName() (string, error) {
svcName := o.ServiceName
if svcName != "" {
if _, exist := o.App.Services[svcName]; exist {
return svcName, nil
}
o.Cmd.Printf("The service name '%s' is not valid\n", svcName)
}
compName, err := util.AskToChooseOneService(appfile.GetComponents(o.App))
if err != nil {
return "", err
}
return compName, nil
}
func (o *VelaExecOptions) getPodName(compName string) (string, error) {
podList, err := o.ClientSet.CoreV1().Pods(o.Env.Namespace).List(o.Context, v1.ListOptions{
LabelSelector: labels.Set(map[string]string{
// TODO(roywang) except core workloads, not any workloads will pass these label to pod
// find a rigorous way to get pod by compname
oam.LabelAppComponent: compName,
}).String(),
})
if err != nil {
return "", nil
}
if podList != nil && len(podList.Items) == 0 {
return "", fmt.Errorf("cannot get pods")
}
for _, p := range podList.Items {
if strings.HasPrefix(p.Name, compName+"-") {
return p.Name, nil
}
}
// if no pod with name matched prefix as component name
// just return the first one
return podList.Items[0].Name, nil
}
// Run executes a validated remote execution against a pod
func (o *VelaExecOptions) Run() error {
return o.kcExecOptions.Run()
}