Add support for replication controllers, deployment configs, jobs,
and daemon sets.
... | ... |
@@ -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" |