Browse code

oc: support multiple resources in rsh

Add support for replication controllers, deployment configs, jobs,
and daemon sets.

kargakis authored on 2016/03/30 01:00:23
Showing 4 changed files
... ...
@@ -1533,6 +1533,12 @@ Start a shell session in a pod
1533 1533
 
1534 1534
   # Run the command 'cat /etc/resolv.conf' inside pod 'foo'
1535 1535
   $ oc rsh foo cat /etc/resolv.conf
1536
+
1537
+  # See the configuration of your internal registry
1538
+  $ oc rsh dc/docker-registry cat config.yml
1539
+
1540
+  # Open a shell session on the container named 'index' inside a pod of your job
1541
+  # oc rsh -c index job/sheduled
1536 1542
 ----
1537 1543
 ====
1538 1544
 
... ...
@@ -3,6 +3,7 @@ package cmd
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"time"
6 7
 
7 8
 	"github.com/spf13/cobra"
8 9
 
... ...
@@ -19,11 +20,13 @@ const (
19 19
 	rshLong = `
20 20
 Open a remote shell session to a container
21 21
 
22
-This command will attempt to start a shell session in the specified pod. It will default to the
23
-first container if none is specified, and will attempt to use '/bin/bash' as the default shell.
24
-You may pass an optional command after the pod name, which will be executed instead of a login
25
-shell. A TTY will be automatically allocated if standard input is interactive - use -t and -T
26
-to override.
22
+This command will attempt to start a shell session in a pod for the specified resource.
23
+It works with pods, deployment configs, jobs, daemon sets, and replication controllers.
24
+Any of the aforementioned resources (apart from pods) will be resolved to a ready pod.
25
+It will default to the first container if none is specified, and will attempt to use
26
+'/bin/bash' as the default shell. You may pass an optional command after the resource name,
27
+which will be executed instead of a login shell. A TTY will be automatically allocated
28
+if standard input is interactive - use -t and -T to override.
27 29
 
28 30
 Note, some containers may not include a shell - use '%[1]s exec' if you need to run commands
29 31
 directly.`
... ...
@@ -33,7 +36,13 @@ directly.`
33 33
   $ %[1]s foo
34 34
 
35 35
   # Run the command 'cat /etc/resolv.conf' inside pod 'foo'
36
-  $ %[1]s foo cat /etc/resolv.conf`
36
+  $ %[1]s foo cat /etc/resolv.conf
37
+
38
+  # See the configuration of your internal registry
39
+  $ %[1]s dc/docker-registry cat config.yml
40
+
41
+  # Open a shell session on the container named 'index' inside a pod of your job
42
+  # %[1]s -c index job/sheduled`
37 43
 )
38 44
 
39 45
 // RshOptions declare the arguments accepted by the Rsh command
... ...
@@ -96,7 +105,7 @@ func (o *RshOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []s
96 96
 	if len(args) < 1 {
97 97
 		return kcmdutil.UsageError(cmd, "rsh requires a single Pod to connect to")
98 98
 	}
99
-	o.PodName = args[0]
99
+	resource := args[0]
100 100
 	args = args[1:]
101 101
 	if len(args) > 0 {
102 102
 		o.Command = args
... ...
@@ -122,7 +131,9 @@ func (o *RshOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []s
122 122
 	}
123 123
 	o.Client = client
124 124
 
125
-	return nil
125
+	// TODO: Consider making the timeout configurable
126
+	o.PodName, err = f.PodForResource(resource, 10*time.Second)
127
+	return err
126 128
 }
127 129
 
128 130
 // Validate ensures that RshOptions are valid
... ...
@@ -18,14 +18,16 @@ import (
18 18
 	"github.com/spf13/pflag"
19 19
 
20 20
 	"k8s.io/kubernetes/pkg/api"
21
-	kerrors "k8s.io/kubernetes/pkg/api/errors"
22 21
 	"k8s.io/kubernetes/pkg/api/meta"
23 22
 	"k8s.io/kubernetes/pkg/api/unversioned"
24 23
 	"k8s.io/kubernetes/pkg/apimachinery/registered"
24
+	"k8s.io/kubernetes/pkg/apis/batch"
25
+	"k8s.io/kubernetes/pkg/apis/extensions"
25 26
 	"k8s.io/kubernetes/pkg/client/restclient"
26 27
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
27 28
 	kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
28 29
 	kclientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
30
+	"k8s.io/kubernetes/pkg/controller"
29 31
 	"k8s.io/kubernetes/pkg/kubectl"
30 32
 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
31 33
 	"k8s.io/kubernetes/pkg/kubectl/resource"
... ...
@@ -41,6 +43,7 @@ import (
41 41
 	buildutil "github.com/openshift/origin/pkg/build/util"
42 42
 	"github.com/openshift/origin/pkg/client"
43 43
 	"github.com/openshift/origin/pkg/cmd/cli/describe"
44
+	"github.com/openshift/origin/pkg/cmd/util"
44 45
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
45 46
 	deploygen "github.com/openshift/origin/pkg/deploy/generator"
46 47
 	deployreaper "github.com/openshift/origin/pkg/deploy/reaper"
... ...
@@ -391,45 +394,16 @@ func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory {
391 391
 	}
392 392
 	kAttachablePodForObjectFunc := w.Factory.AttachablePodForObject
393 393
 	w.AttachablePodForObject = func(object runtime.Object) (*api.Pod, error) {
394
-		oc, kc, err := w.Clients()
395
-		if err != nil {
396
-			return nil, err
397
-		}
398 394
 		switch t := object.(type) {
399 395
 		case *deployapi.DeploymentConfig:
400
-			var err error
401
-			var pods *api.PodList
402
-			for pods == nil || len(pods.Items) == 0 {
403
-				if t.Status.LatestVersion == 0 {
404
-					time.Sleep(2 * time.Second)
405
-				}
406
-				if t, err = oc.DeploymentConfigs(t.Namespace).Get(t.Name); err != nil {
407
-					return nil, err
408
-				}
409
-				latestDeploymentName := deployutil.LatestDeploymentNameForConfig(t)
410
-				deployment, err := kc.ReplicationControllers(t.Namespace).Get(latestDeploymentName)
411
-				if err != nil {
412
-					if kerrors.IsNotFound(err) {
413
-						continue
414
-					}
415
-					return nil, err
416
-				}
417
-				pods, err = kc.Pods(deployment.Namespace).List(api.ListOptions{LabelSelector: labels.SelectorFromSet(deployment.Spec.Selector)})
418
-				if err != nil {
419
-					return nil, err
420
-				}
421
-				if len(pods.Items) == 0 {
422
-					time.Sleep(2 * time.Second)
423
-				}
424
-			}
425
-			var oldestPod *api.Pod
426
-			for i := range pods.Items {
427
-				pod := &pods.Items[i]
428
-				if oldestPod == nil || pod.CreationTimestamp.Before(oldestPod.CreationTimestamp) {
429
-					oldestPod = pod
430
-				}
396
+			_, kc, err := w.Clients()
397
+			if err != nil {
398
+				return nil, err
431 399
 			}
432
-			return oldestPod, nil
400
+			selector := labels.SelectorFromSet(t.Spec.Selector)
401
+			f := func(pods []*api.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
402
+			pod, _, err := cmdutil.GetFirstPod(kc, t.Namespace, selector, 1*time.Minute, f)
403
+			return pod, err
433 404
 		default:
434 405
 			return kAttachablePodForObjectFunc(object)
435 406
 		}
... ...
@@ -578,6 +552,90 @@ func (w *Factory) ApproximatePodTemplateForObject(object runtime.Object) (*api.P
578 578
 	}
579 579
 }
580 580
 
581
+func (f *Factory) PodForResource(resource string, timeout time.Duration) (string, error) {
582
+	sortBy := func(pods []*api.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
583
+	namespace, _, err := f.DefaultNamespace()
584
+	if err != nil {
585
+		return "", err
586
+	}
587
+	oc, kc, err := f.Clients()
588
+	if err != nil {
589
+		return "", err
590
+	}
591
+	mapper, _ := f.Object(false)
592
+	resourceType, name, err := util.ResolveResource(api.Resource("pods"), resource, mapper)
593
+	if err != nil {
594
+		return "", err
595
+	}
596
+
597
+	switch resourceType {
598
+	case api.Resource("pods"):
599
+		return name, nil
600
+	case api.Resource("replicationcontrollers"):
601
+		rc, err := kc.ReplicationControllers(namespace).Get(name)
602
+		if err != nil {
603
+			return "", err
604
+		}
605
+		selector := labels.SelectorFromSet(rc.Spec.Selector)
606
+		pod, _, err := cmdutil.GetFirstPod(kc, namespace, selector, timeout, sortBy)
607
+		if err != nil {
608
+			return "", err
609
+		}
610
+		return pod.Name, nil
611
+	case deployapi.Resource("deploymentconfigs"):
612
+		dc, err := oc.DeploymentConfigs(namespace).Get(name)
613
+		if err != nil {
614
+			return "", err
615
+		}
616
+		selector := labels.SelectorFromSet(dc.Spec.Selector)
617
+		pod, _, err := cmdutil.GetFirstPod(kc, namespace, selector, timeout, sortBy)
618
+		if err != nil {
619
+			return "", err
620
+		}
621
+		return pod.Name, nil
622
+	case extensions.Resource("daemonsets"):
623
+		ds, err := kc.Extensions().DaemonSets(namespace).Get(name)
624
+		if err != nil {
625
+			return "", err
626
+		}
627
+		selector, err := unversioned.LabelSelectorAsSelector(ds.Spec.Selector)
628
+		if err != nil {
629
+			return "", err
630
+		}
631
+		pod, _, err := cmdutil.GetFirstPod(kc, namespace, selector, timeout, sortBy)
632
+		if err != nil {
633
+			return "", err
634
+		}
635
+		return pod.Name, nil
636
+	case extensions.Resource("jobs"):
637
+		job, err := kc.Extensions().Jobs(namespace).Get(name)
638
+		if err != nil {
639
+			return "", err
640
+		}
641
+		return podNameForJob(job, kc, timeout, sortBy)
642
+	case batch.Resource("jobs"):
643
+		job, err := kc.Batch().Jobs(namespace).Get(name)
644
+		if err != nil {
645
+			return "", err
646
+		}
647
+		return podNameForJob(job, kc, timeout, sortBy)
648
+	default:
649
+		return "", fmt.Errorf("remote shell for %s is not supported", resourceType)
650
+	}
651
+}
652
+
653
+func podNameForJob(job *extensions.Job, kc *kclient.Client, timeout time.Duration, sortBy func(pods []*api.Pod) sort.Interface) (string, error) {
654
+	selector, err := unversioned.LabelSelectorAsSelector(job.Spec.Selector)
655
+	if err != nil {
656
+		return "", err
657
+	}
658
+	pod, _, err := cmdutil.GetFirstPod(kc, job.Namespace, selector, timeout, sortBy)
659
+	if err != nil {
660
+		return "", err
661
+	}
662
+	return pod.Name, nil
663
+}
664
+
581 665
 // Clients returns an OpenShift and Kubernetes client.
582 666
 func (f *Factory) Clients() (*client.Client, *kclient.Client, error) {
583 667
 	kClient, err := f.Client()
... ...
@@ -90,7 +90,10 @@ echo "[INFO] Waiting for Docker registry pod to start"
90 90
 wait_for_registry
91 91
 
92 92
 # check to make sure that logs for rc works
93
-oc logs rc/docker-registry-1 > /dev/null
93
+os::cmd::expect_success "oc logs rc/docker-registry-1 > /dev/null"
94
+# check that we can get a remote shell to a dc or rc
95
+os::cmd::expect_success_and_text "oc rsh dc/docker-registry cat config.yml" "5000"
96
+os::cmd::expect_success_and_text "oc rsh rc/docker-registry-1 cat config.yml" "5000"
94 97
 
95 98
 # services can end up on any IP.  Make sure we get the IP we need for the docker registry
96 99
 DOCKER_REGISTRY=$(oc get --output-version=v1beta3 --template="{{ .spec.portalIP }}:{{ with index .spec.ports 0 }}{{ .port }}{{ end }}" service docker-registry)
... ...
@@ -263,7 +266,7 @@ frontend_pod=$(oc get pod -l deploymentconfig=frontend --template='{{(index .ite
263 263
 # when running as a restricted pod the registry will run with a pre-allocated
264 264
 # user in the neighborhood of 1000000+.  Look for a substring of the pre-allocated uid range
265 265
 os::cmd::expect_success_and_text "oc exec -p ${frontend_pod} id" '1000'
266
-os::cmd::expect_success_and_text "oc rsh ${frontend_pod} id -u" '1000'
266
+os::cmd::expect_success_and_text "oc rsh pod/${frontend_pod} id -u" '1000'
267 267
 os::cmd::expect_success_and_text "oc rsh -T ${frontend_pod} id -u" '1000'
268 268
 # Test retrieving application logs from dc
269 269
 oc logs dc/frontend | grep "Connecting to production database"