|
|
@@ -5,17 +5,22 @@ import (
|
|
|
"fmt"
|
|
|
"os"
|
|
|
"strings"
|
|
|
+ "time"
|
|
|
|
|
|
"github.com/fatih/color"
|
|
|
"github.com/porter-dev/porter/cli/cmd/api"
|
|
|
"github.com/porter-dev/porter/cli/cmd/utils"
|
|
|
"github.com/spf13/cobra"
|
|
|
+ v1 "k8s.io/api/core/v1"
|
|
|
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
+ "k8s.io/kubectl/pkg/util/term"
|
|
|
+
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
+ "k8s.io/client-go/kubernetes"
|
|
|
"k8s.io/client-go/rest"
|
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
|
"k8s.io/client-go/tools/remotecommand"
|
|
|
- "k8s.io/kubectl/pkg/util/term"
|
|
|
)
|
|
|
|
|
|
var namespace string
|
|
|
@@ -35,6 +40,8 @@ var runCmd = &cobra.Command{
|
|
|
},
|
|
|
}
|
|
|
|
|
|
+var existingPod bool
|
|
|
+
|
|
|
func init() {
|
|
|
rootCmd.AddCommand(runCmd)
|
|
|
|
|
|
@@ -44,10 +51,19 @@ func init() {
|
|
|
"default",
|
|
|
"namespace of release to connect to",
|
|
|
)
|
|
|
+
|
|
|
+ runCmd.PersistentFlags().BoolVarP(
|
|
|
+ &existingPod,
|
|
|
+ "existing_pod",
|
|
|
+ "e",
|
|
|
+ false,
|
|
|
+ "whether to connect to an existing pod",
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
func run(_ *api.AuthCheckResponse, client *api.Client, args []string) error {
|
|
|
color.New(color.FgGreen).Println("Running", strings.Join(args[1:], " "), "for release", args[0])
|
|
|
+ color.New(color.FgGreen).Println("If you don't see a command prompt, try pressing enter.")
|
|
|
|
|
|
podsSimple, err := getPods(client, namespace, args[0])
|
|
|
|
|
|
@@ -106,7 +122,11 @@ func run(_ *api.AuthCheckResponse, client *api.Client, args []string) error {
|
|
|
return fmt.Errorf("Could not retrieve kube credentials: %s", err.Error())
|
|
|
}
|
|
|
|
|
|
- return executeRun(restConf, namespace, selectedPod.Name, selectedContainerName, args[1:])
|
|
|
+ if existingPod {
|
|
|
+ return executeRun(restConf, namespace, selectedPod.Name, selectedContainerName, args[1:])
|
|
|
+ }
|
|
|
+
|
|
|
+ return executeRunEphemeral(restConf, namespace, selectedPod.Name, selectedContainerName, args[1:])
|
|
|
}
|
|
|
|
|
|
func getRESTConfig(client *api.Client) (*rest.Config, error) {
|
|
|
@@ -189,7 +209,6 @@ func executeRun(config *rest.Config, namespace, name, container string, args []s
|
|
|
Namespace(namespace).
|
|
|
SubResource("exec")
|
|
|
|
|
|
- // req.Param("container", "web")
|
|
|
for _, arg := range args {
|
|
|
req.Param("command", arg)
|
|
|
}
|
|
|
@@ -225,3 +244,144 @@ func executeRun(config *rest.Config, namespace, name, container string, args []s
|
|
|
|
|
|
return err
|
|
|
}
|
|
|
+
|
|
|
+func executeRunEphemeral(config *rest.Config, namespace, name, container string, args []string) error {
|
|
|
+ existing, err := getExistingPod(config, name, namespace)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ newPod, err := createPodFromExisting(config, existing, args)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ podName := newPod.ObjectMeta.Name
|
|
|
+
|
|
|
+ t := term.TTY{
|
|
|
+ In: os.Stdin,
|
|
|
+ Out: os.Stdout,
|
|
|
+ Raw: true,
|
|
|
+ }
|
|
|
+
|
|
|
+ fn := func() error {
|
|
|
+ restClient, err := rest.RESTClientFor(config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ req := restClient.Post().
|
|
|
+ Resource("pods").
|
|
|
+ Name(podName).
|
|
|
+ Namespace("default").
|
|
|
+ SubResource("attach")
|
|
|
+
|
|
|
+ req.Param("stdin", "true")
|
|
|
+ req.Param("stdout", "true")
|
|
|
+ req.Param("tty", "true")
|
|
|
+ req.Param("container", container)
|
|
|
+
|
|
|
+ exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return exec.Stream(remotecommand.StreamOptions{
|
|
|
+ Stdin: os.Stdin,
|
|
|
+ Stdout: os.Stdout,
|
|
|
+ Stderr: os.Stderr,
|
|
|
+ Tty: true,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := 0; i < 5; i++ {
|
|
|
+ fmt.Printf("attempting connection %d/5\n", i)
|
|
|
+
|
|
|
+ err = t.Safe(fn)
|
|
|
+
|
|
|
+ if err == nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ time.Sleep(2 * time.Second)
|
|
|
+ }
|
|
|
+
|
|
|
+ // delete the ephemeral pod
|
|
|
+ deletePod(config, podName, namespace)
|
|
|
+
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+func getExistingPod(config *rest.Config, name, namespace string) (*v1.Pod, error) {
|
|
|
+ clientset, err := kubernetes.NewForConfig(config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return clientset.CoreV1().Pods(namespace).Get(
|
|
|
+ context.Background(),
|
|
|
+ name,
|
|
|
+ metav1.GetOptions{},
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+func deletePod(config *rest.Config, name, namespace string) error {
|
|
|
+ clientset, err := kubernetes.NewForConfig(config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return clientset.CoreV1().Pods(namespace).Delete(
|
|
|
+ context.Background(),
|
|
|
+ name,
|
|
|
+ metav1.DeleteOptions{},
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+func createPodFromExisting(config *rest.Config, existing *v1.Pod, args []string) (*v1.Pod, error) {
|
|
|
+ clientset, err := kubernetes.NewForConfig(config)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ newPod := existing.DeepCopy()
|
|
|
+
|
|
|
+ // only copy the pod spec, overwrite metadata
|
|
|
+ newPod.ObjectMeta = metav1.ObjectMeta{
|
|
|
+ Name: strings.ToLower(fmt.Sprintf("%s-copy-%s", existing.ObjectMeta.Name, utils.String(4))),
|
|
|
+ Namespace: existing.ObjectMeta.Namespace,
|
|
|
+ }
|
|
|
+
|
|
|
+ newPod.Status = v1.PodStatus{}
|
|
|
+
|
|
|
+ // set restart policy to never
|
|
|
+ newPod.Spec.RestartPolicy = v1.RestartPolicyNever
|
|
|
+
|
|
|
+ // change the command in the pod to the passed in pod command
|
|
|
+ cmdRoot := args[0]
|
|
|
+ cmdArgs := make([]string, 0)
|
|
|
+
|
|
|
+ if len(args) > 1 {
|
|
|
+ cmdArgs = args[1:]
|
|
|
+ }
|
|
|
+
|
|
|
+ newPod.Spec.Containers[0].Command = []string{cmdRoot}
|
|
|
+ newPod.Spec.Containers[0].Args = cmdArgs
|
|
|
+ newPod.Spec.Containers[0].TTY = true
|
|
|
+ newPod.Spec.Containers[0].Stdin = true
|
|
|
+ newPod.Spec.Containers[0].StdinOnce = true
|
|
|
+
|
|
|
+ // create the pod and return it
|
|
|
+ return clientset.CoreV1().Pods(existing.ObjectMeta.Namespace).Create(
|
|
|
+ context.Background(),
|
|
|
+ newPod,
|
|
|
+ metav1.CreateOptions{},
|
|
|
+ )
|
|
|
+}
|