2014-06-09 04:42:09 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2014-06-09 04:42:09 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
2014-06-23 18:32:11 +00:00
|
|
|
|
2014-08-11 07:34:59 +00:00
|
|
|
package pod
|
2014-06-09 04:42:09 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-03-23 18:42:39 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2015-09-10 03:46:11 +00:00
|
|
|
"strconv"
|
2016-01-11 14:26:44 +00:00
|
|
|
"strings"
|
2015-09-10 03:46:11 +00:00
|
|
|
"time"
|
2014-06-09 04:42:09 +00:00
|
|
|
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/errors"
|
|
|
|
"k8s.io/kubernetes/pkg/api/validation"
|
|
|
|
"k8s.io/kubernetes/pkg/fields"
|
2015-10-27 13:18:45 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
|
|
|
"k8s.io/kubernetes/pkg/registry/generic"
|
|
|
|
"k8s.io/kubernetes/pkg/runtime"
|
2016-06-03 13:23:26 +00:00
|
|
|
"k8s.io/kubernetes/pkg/storage"
|
2016-01-06 15:56:41 +00:00
|
|
|
utilnet "k8s.io/kubernetes/pkg/util/net"
|
2015-11-06 23:30:52 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/validation/field"
|
2014-06-09 04:42:09 +00:00
|
|
|
)
|
|
|
|
|
2015-02-12 00:19:11 +00:00
|
|
|
// podStrategy implements behavior for Pods
|
|
|
|
type podStrategy struct {
|
|
|
|
runtime.ObjectTyper
|
|
|
|
api.NameGenerator
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strategy is the default logic that applies when creating and updating Pod
|
|
|
|
// objects via the REST API.
|
|
|
|
var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator}
|
|
|
|
|
|
|
|
// NamespaceScoped is true for pods.
|
|
|
|
func (podStrategy) NamespaceScoped() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-03-25 21:45:07 +00:00
|
|
|
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
|
2016-08-08 20:15:33 +00:00
|
|
|
func (podStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
|
2015-02-12 00:19:11 +00:00
|
|
|
pod := obj.(*api.Pod)
|
|
|
|
pod.Status = api.PodStatus{
|
|
|
|
Phase: api.PodPending,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-26 00:03:30 +00:00
|
|
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
2016-08-08 20:15:33 +00:00
|
|
|
func (podStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
|
2015-03-26 00:03:30 +00:00
|
|
|
newPod := obj.(*api.Pod)
|
|
|
|
oldPod := old.(*api.Pod)
|
|
|
|
newPod.Status = oldPod.Status
|
|
|
|
}
|
|
|
|
|
2015-02-12 00:19:11 +00:00
|
|
|
// Validate validates a new pod.
|
2015-11-06 23:30:52 +00:00
|
|
|
func (podStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
|
2015-02-12 00:19:11 +00:00
|
|
|
pod := obj.(*api.Pod)
|
|
|
|
return validation.ValidatePod(pod)
|
|
|
|
}
|
|
|
|
|
2015-11-13 05:13:16 +00:00
|
|
|
// Canonicalize normalizes the object after validation.
|
|
|
|
func (podStrategy) Canonicalize(obj runtime.Object) {
|
|
|
|
}
|
|
|
|
|
2015-02-12 00:19:11 +00:00
|
|
|
// AllowCreateOnUpdate is false for pods.
|
|
|
|
func (podStrategy) AllowCreateOnUpdate() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateUpdate is the default update validation for an end user.
|
2015-11-06 23:30:52 +00:00
|
|
|
func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
|
2015-04-09 03:15:49 +00:00
|
|
|
errorList := validation.ValidatePod(obj.(*api.Pod))
|
|
|
|
return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
|
2015-02-12 00:19:11 +00:00
|
|
|
}
|
|
|
|
|
2015-08-20 02:18:59 +00:00
|
|
|
// AllowUnconditionalUpdate allows pods to be overwritten
|
2015-06-19 00:42:01 +00:00
|
|
|
func (podStrategy) AllowUnconditionalUpdate() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-08-20 02:18:59 +00:00
|
|
|
// CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to
|
|
|
|
// reflect the desired grace value.
|
2016-08-08 20:15:33 +00:00
|
|
|
func (podStrategy) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
|
2015-08-20 02:18:59 +00:00
|
|
|
if options == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
pod := obj.(*api.Pod)
|
|
|
|
period := int64(0)
|
|
|
|
// user has specified a value
|
|
|
|
if options.GracePeriodSeconds != nil {
|
|
|
|
period = *options.GracePeriodSeconds
|
|
|
|
} else {
|
|
|
|
// use the default value if set, or deletes the pod immediately (0)
|
|
|
|
if pod.Spec.TerminationGracePeriodSeconds != nil {
|
|
|
|
period = *pod.Spec.TerminationGracePeriodSeconds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if the pod is not scheduled, delete immediately
|
|
|
|
if len(pod.Spec.NodeName) == 0 {
|
|
|
|
period = 0
|
|
|
|
}
|
2015-10-19 23:03:14 +00:00
|
|
|
// if the pod is already terminated, delete immediately
|
|
|
|
if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
|
|
|
|
period = 0
|
|
|
|
}
|
2015-08-20 02:18:59 +00:00
|
|
|
// ensure the options and the pod are in sync
|
|
|
|
options.GracePeriodSeconds = &period
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type podStrategyWithoutGraceful struct {
|
|
|
|
podStrategy
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckGracefulDelete prohibits graceful deletion.
|
2016-08-08 20:15:33 +00:00
|
|
|
func (podStrategyWithoutGraceful) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
|
2015-07-27 20:41:31 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-08-20 02:18:59 +00:00
|
|
|
// StrategyWithoutGraceful implements the legacy instant delele behavior.
|
|
|
|
var StrategyWithoutGraceful = podStrategyWithoutGraceful{Strategy}
|
|
|
|
|
2015-02-24 05:42:27 +00:00
|
|
|
type podStatusStrategy struct {
|
|
|
|
podStrategy
|
|
|
|
}
|
|
|
|
|
|
|
|
var StatusStrategy = podStatusStrategy{Strategy}
|
|
|
|
|
2016-08-08 20:15:33 +00:00
|
|
|
func (podStatusStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
|
2015-03-26 00:03:30 +00:00
|
|
|
newPod := obj.(*api.Pod)
|
|
|
|
oldPod := old.(*api.Pod)
|
|
|
|
newPod.Spec = oldPod.Spec
|
2015-08-19 23:59:43 +00:00
|
|
|
newPod.DeletionTimestamp = nil
|
2015-03-26 00:03:30 +00:00
|
|
|
}
|
|
|
|
|
2015-11-06 23:30:52 +00:00
|
|
|
func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
|
2015-02-24 05:42:27 +00:00
|
|
|
// TODO: merge valid fields after update
|
|
|
|
return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
|
|
|
|
}
|
|
|
|
|
2015-02-11 23:37:12 +00:00
|
|
|
// MatchPod returns a generic matcher for a given label and field selector.
|
2016-08-23 03:41:21 +00:00
|
|
|
func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
|
|
|
|
return storage.SelectionPredicate{
|
2015-04-03 21:10:47 +00:00
|
|
|
Label: label,
|
|
|
|
Field: field,
|
|
|
|
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
|
|
|
|
pod, ok := obj.(*api.Pod)
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, fmt.Errorf("not a pod")
|
|
|
|
}
|
2016-04-04 17:29:52 +00:00
|
|
|
|
|
|
|
// podLabels is already sitting there ready to be used.
|
|
|
|
// podFields is not available directly and requires allocation of a map.
|
|
|
|
// Only bother if the fields might be useful to determining the match.
|
|
|
|
// One common case is for a replication controller to set up a watch
|
|
|
|
// based on labels alone; in that case we can avoid allocating the field map.
|
|
|
|
// This is especially important in the apiserver.
|
|
|
|
podLabels := labels.Set(pod.ObjectMeta.Labels)
|
|
|
|
var podFields fields.Set
|
|
|
|
if !field.Empty() && label.Matches(podLabels) {
|
|
|
|
podFields = PodToSelectableFields(pod)
|
|
|
|
}
|
|
|
|
return podLabels, podFields, nil
|
2015-04-03 21:10:47 +00:00
|
|
|
},
|
2016-06-03 13:23:26 +00:00
|
|
|
IndexFields: []string{"spec.nodeName"},
|
2015-04-03 21:10:47 +00:00
|
|
|
}
|
2014-06-09 04:42:09 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 13:23:26 +00:00
|
|
|
func NodeNameTriggerFunc(obj runtime.Object) []storage.MatchValue {
|
|
|
|
pod := obj.(*api.Pod)
|
|
|
|
result := storage.MatchValue{IndexName: "spec.nodeName", Value: pod.Spec.NodeName}
|
|
|
|
return []storage.MatchValue{result}
|
|
|
|
}
|
|
|
|
|
2015-10-15 23:53:26 +00:00
|
|
|
// PodToSelectableFields returns a field set that represents the object
|
2015-02-11 23:37:12 +00:00
|
|
|
// TODO: fields are not labels, and the validation rules for them do not apply.
|
2015-04-03 21:10:47 +00:00
|
|
|
func PodToSelectableFields(pod *api.Pod) fields.Set {
|
2016-08-23 14:34:02 +00:00
|
|
|
objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true)
|
2015-10-15 23:53:26 +00:00
|
|
|
podSpecificFieldsSet := fields.Set{
|
2016-01-17 19:59:56 +00:00
|
|
|
"spec.nodeName": pod.Spec.NodeName,
|
|
|
|
"spec.restartPolicy": string(pod.Spec.RestartPolicy),
|
|
|
|
"status.phase": string(pod.Status.Phase),
|
2014-09-16 23:15:40 +00:00
|
|
|
}
|
2015-10-15 23:53:26 +00:00
|
|
|
return generic.MergeFieldsSets(objectMetaFieldsSet, podSpecificFieldsSet)
|
2014-09-16 23:15:40 +00:00
|
|
|
}
|
|
|
|
|
2015-02-11 23:37:12 +00:00
|
|
|
// ResourceGetter is an interface for retrieving resources by ResourceLocation.
|
|
|
|
type ResourceGetter interface {
|
|
|
|
Get(api.Context, string) (runtime.Object, error)
|
2014-08-11 07:34:59 +00:00
|
|
|
}
|
2014-12-22 23:02:16 +00:00
|
|
|
|
2015-04-06 18:57:06 +00:00
|
|
|
func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
|
|
|
|
obj, err := getter.Get(ctx, name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pod := obj.(*api.Pod)
|
|
|
|
if pod == nil {
|
|
|
|
return nil, fmt.Errorf("Unexpected object type: %#v", pod)
|
|
|
|
}
|
|
|
|
return pod, nil
|
|
|
|
}
|
|
|
|
|
2014-12-22 23:02:16 +00:00
|
|
|
// ResourceLocation returns a URL to which one can send traffic for the specified pod.
|
2015-10-09 05:18:16 +00:00
|
|
|
func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
|
2015-10-09 06:25:25 +00:00
|
|
|
// Allow ID as "podname" or "podname:port" or "scheme:podname:port".
|
|
|
|
// If port is not specified, try to use the first defined port on the pod.
|
2016-01-06 15:56:41 +00:00
|
|
|
scheme, name, port, valid := utilnet.SplitSchemeNamePort(id)
|
2015-04-20 21:31:44 +00:00
|
|
|
if !valid {
|
2015-03-23 18:42:39 +00:00
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
|
2014-12-22 23:02:16 +00:00
|
|
|
}
|
2015-04-20 21:31:44 +00:00
|
|
|
// TODO: if port is not a number but a "(container)/(portname)", do a name lookup.
|
2014-12-22 23:02:16 +00:00
|
|
|
|
2015-04-06 18:57:06 +00:00
|
|
|
pod, err := getPod(getter, ctx, name)
|
2014-12-22 23:02:16 +00:00
|
|
|
if err != nil {
|
2015-03-23 18:42:39 +00:00
|
|
|
return nil, nil, err
|
2014-12-22 23:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Try to figure out a port.
|
|
|
|
if port == "" {
|
|
|
|
for i := range pod.Spec.Containers {
|
|
|
|
if len(pod.Spec.Containers[i].Ports) > 0 {
|
|
|
|
port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-09 06:25:25 +00:00
|
|
|
loc := &url.URL{
|
|
|
|
Scheme: scheme,
|
|
|
|
}
|
2015-03-23 18:42:39 +00:00
|
|
|
if port == "" {
|
|
|
|
loc.Host = pod.Status.PodIP
|
|
|
|
} else {
|
|
|
|
loc.Host = net.JoinHostPort(pod.Status.PodIP, port)
|
2014-12-22 23:02:16 +00:00
|
|
|
}
|
2015-10-09 05:18:16 +00:00
|
|
|
return loc, rt, nil
|
2014-12-22 23:02:16 +00:00
|
|
|
}
|
2015-04-06 18:57:06 +00:00
|
|
|
|
2016-01-11 14:26:44 +00:00
|
|
|
// getContainerNames returns a formatted string containing the container names
|
2016-06-09 12:08:43 +00:00
|
|
|
func getContainerNames(containers []api.Container) string {
|
2016-01-11 14:26:44 +00:00
|
|
|
names := []string{}
|
2016-06-09 12:08:43 +00:00
|
|
|
for _, c := range containers {
|
2016-01-11 14:26:44 +00:00
|
|
|
names = append(names, c.Name)
|
|
|
|
}
|
|
|
|
return strings.Join(names, " ")
|
|
|
|
}
|
|
|
|
|
2015-09-09 03:40:36 +00:00
|
|
|
// LogLocation returns the log URL for a pod container. If opts.Container is blank
|
2015-04-06 18:57:06 +00:00
|
|
|
// and only one container is present in the pod, that container is used.
|
2015-10-27 13:18:45 +00:00
|
|
|
func LogLocation(
|
|
|
|
getter ResourceGetter,
|
|
|
|
connInfo client.ConnectionInfoGetter,
|
|
|
|
ctx api.Context,
|
|
|
|
name string,
|
|
|
|
opts *api.PodLogOptions,
|
|
|
|
) (*url.URL, http.RoundTripper, error) {
|
2015-04-06 18:57:06 +00:00
|
|
|
pod, err := getPod(getter, ctx, name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to figure out a container
|
2015-11-27 18:19:36 +00:00
|
|
|
// If a container was provided, it must be valid
|
2015-04-06 18:57:06 +00:00
|
|
|
container := opts.Container
|
2015-11-27 18:19:36 +00:00
|
|
|
if len(container) == 0 {
|
2016-01-11 14:26:44 +00:00
|
|
|
switch len(pod.Spec.Containers) {
|
|
|
|
case 1:
|
2015-04-06 18:57:06 +00:00
|
|
|
container = pod.Spec.Containers[0].Name
|
2016-01-11 14:26:44 +00:00
|
|
|
case 0:
|
2015-04-14 15:12:27 +00:00
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
|
2016-01-11 14:26:44 +00:00
|
|
|
default:
|
2016-06-09 12:08:43 +00:00
|
|
|
containerNames := getContainerNames(pod.Spec.Containers)
|
|
|
|
initContainerNames := getContainerNames(pod.Spec.InitContainers)
|
|
|
|
err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
|
|
|
|
if len(initContainerNames) > 0 {
|
|
|
|
err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
|
|
|
|
}
|
|
|
|
return nil, nil, errors.NewBadRequest(err)
|
2015-04-06 18:57:06 +00:00
|
|
|
}
|
2015-11-27 18:19:36 +00:00
|
|
|
} else {
|
|
|
|
if !podHasContainerWithName(pod, container) {
|
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
|
|
|
|
}
|
2015-04-06 18:57:06 +00:00
|
|
|
}
|
2015-05-22 23:40:57 +00:00
|
|
|
nodeHost := pod.Spec.NodeName
|
2015-04-06 18:57:06 +00:00
|
|
|
if len(nodeHost) == 0 {
|
|
|
|
// If pod has not been assigned a host, return an empty location
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
2015-10-27 13:18:45 +00:00
|
|
|
nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
|
2015-04-06 18:57:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
params := url.Values{}
|
|
|
|
if opts.Follow {
|
|
|
|
params.Add("follow", "true")
|
|
|
|
}
|
2015-05-07 18:34:16 +00:00
|
|
|
if opts.Previous {
|
|
|
|
params.Add("previous", "true")
|
|
|
|
}
|
2015-09-10 03:46:11 +00:00
|
|
|
if opts.Timestamps {
|
|
|
|
params.Add("timestamps", "true")
|
|
|
|
}
|
|
|
|
if opts.SinceSeconds != nil {
|
|
|
|
params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10))
|
|
|
|
}
|
|
|
|
if opts.SinceTime != nil {
|
|
|
|
params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339))
|
|
|
|
}
|
|
|
|
if opts.TailLines != nil {
|
|
|
|
params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10))
|
|
|
|
}
|
|
|
|
if opts.LimitBytes != nil {
|
|
|
|
params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10))
|
|
|
|
}
|
2015-04-06 18:57:06 +00:00
|
|
|
loc := &url.URL{
|
|
|
|
Scheme: nodeScheme,
|
|
|
|
Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
|
2015-11-27 18:19:36 +00:00
|
|
|
Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
|
2015-04-06 18:57:06 +00:00
|
|
|
RawQuery: params.Encode(),
|
|
|
|
}
|
|
|
|
return loc, nodeTransport, nil
|
|
|
|
}
|
2015-04-14 15:12:27 +00:00
|
|
|
|
2015-11-27 18:19:36 +00:00
|
|
|
func podHasContainerWithName(pod *api.Pod, containerName string) bool {
|
|
|
|
for _, c := range pod.Spec.Containers {
|
|
|
|
if c.Name == containerName {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2016-06-03 06:05:15 +00:00
|
|
|
for _, c := range pod.Spec.InitContainers {
|
|
|
|
if c.Name == containerName {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2015-11-27 18:19:36 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-07-28 22:56:27 +00:00
|
|
|
func streamParams(params url.Values, opts runtime.Object) error {
|
|
|
|
switch opts := opts.(type) {
|
|
|
|
case *api.PodExecOptions:
|
|
|
|
if opts.Stdin {
|
|
|
|
params.Add(api.ExecStdinParam, "1")
|
|
|
|
}
|
|
|
|
if opts.Stdout {
|
|
|
|
params.Add(api.ExecStdoutParam, "1")
|
|
|
|
}
|
|
|
|
if opts.Stderr {
|
|
|
|
params.Add(api.ExecStderrParam, "1")
|
|
|
|
}
|
|
|
|
if opts.TTY {
|
|
|
|
params.Add(api.ExecTTYParam, "1")
|
|
|
|
}
|
|
|
|
for _, c := range opts.Command {
|
|
|
|
params.Add("command", c)
|
|
|
|
}
|
|
|
|
case *api.PodAttachOptions:
|
|
|
|
if opts.Stdin {
|
|
|
|
params.Add(api.ExecStdinParam, "1")
|
|
|
|
}
|
|
|
|
if opts.Stdout {
|
|
|
|
params.Add(api.ExecStdoutParam, "1")
|
|
|
|
}
|
|
|
|
if opts.Stderr {
|
|
|
|
params.Add(api.ExecStderrParam, "1")
|
|
|
|
}
|
|
|
|
if opts.TTY {
|
|
|
|
params.Add(api.ExecTTYParam, "1")
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Unknown object for streaming: %v", opts)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AttachLocation returns the attach URL for a pod container. If opts.Container is blank
|
|
|
|
// and only one container is present in the pod, that container is used.
|
2015-10-27 13:18:45 +00:00
|
|
|
func AttachLocation(
|
|
|
|
getter ResourceGetter,
|
|
|
|
connInfo client.ConnectionInfoGetter,
|
|
|
|
ctx api.Context,
|
|
|
|
name string,
|
|
|
|
opts *api.PodAttachOptions,
|
|
|
|
) (*url.URL, http.RoundTripper, error) {
|
2015-10-23 04:25:38 +00:00
|
|
|
return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "attach")
|
2015-07-28 22:56:27 +00:00
|
|
|
}
|
|
|
|
|
2015-04-14 15:12:27 +00:00
|
|
|
// ExecLocation returns the exec URL for a pod container. If opts.Container is blank
|
|
|
|
// and only one container is present in the pod, that container is used.
|
2015-10-27 13:18:45 +00:00
|
|
|
func ExecLocation(
|
|
|
|
getter ResourceGetter,
|
|
|
|
connInfo client.ConnectionInfoGetter,
|
|
|
|
ctx api.Context,
|
|
|
|
name string,
|
|
|
|
opts *api.PodExecOptions,
|
|
|
|
) (*url.URL, http.RoundTripper, error) {
|
2015-10-23 04:25:38 +00:00
|
|
|
return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "exec")
|
2015-07-28 22:56:27 +00:00
|
|
|
}
|
2015-04-14 15:12:27 +00:00
|
|
|
|
2015-10-27 13:18:45 +00:00
|
|
|
func streamLocation(
|
|
|
|
getter ResourceGetter,
|
|
|
|
connInfo client.ConnectionInfoGetter,
|
|
|
|
ctx api.Context,
|
|
|
|
name string,
|
|
|
|
opts runtime.Object,
|
|
|
|
container,
|
|
|
|
path string,
|
|
|
|
) (*url.URL, http.RoundTripper, error) {
|
2015-04-14 15:12:27 +00:00
|
|
|
pod, err := getPod(getter, ctx, name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to figure out a container
|
2015-11-27 18:19:36 +00:00
|
|
|
// If a container was provided, it must be valid
|
2015-04-14 15:12:27 +00:00
|
|
|
if container == "" {
|
2016-01-11 14:26:44 +00:00
|
|
|
switch len(pod.Spec.Containers) {
|
|
|
|
case 1:
|
2015-04-14 15:12:27 +00:00
|
|
|
container = pod.Spec.Containers[0].Name
|
2016-01-11 14:26:44 +00:00
|
|
|
case 0:
|
2015-04-14 15:12:27 +00:00
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
|
2016-01-11 14:26:44 +00:00
|
|
|
default:
|
2016-06-09 12:08:43 +00:00
|
|
|
containerNames := getContainerNames(pod.Spec.Containers)
|
|
|
|
initContainerNames := getContainerNames(pod.Spec.InitContainers)
|
|
|
|
err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
|
|
|
|
if len(initContainerNames) > 0 {
|
|
|
|
err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
|
|
|
|
}
|
|
|
|
return nil, nil, errors.NewBadRequest(err)
|
2015-04-14 15:12:27 +00:00
|
|
|
}
|
2015-11-27 18:19:36 +00:00
|
|
|
} else {
|
|
|
|
if !podHasContainerWithName(pod, container) {
|
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
|
|
|
|
}
|
2015-04-14 15:12:27 +00:00
|
|
|
}
|
2015-05-22 23:40:57 +00:00
|
|
|
nodeHost := pod.Spec.NodeName
|
2015-04-14 15:12:27 +00:00
|
|
|
if len(nodeHost) == 0 {
|
|
|
|
// If pod has not been assigned a host, return an empty location
|
2015-06-29 23:01:01 +00:00
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
|
2015-04-14 15:12:27 +00:00
|
|
|
}
|
2015-10-27 13:18:45 +00:00
|
|
|
nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
|
2015-04-14 15:12:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
params := url.Values{}
|
2015-07-28 22:56:27 +00:00
|
|
|
if err := streamParams(params, opts); err != nil {
|
|
|
|
return nil, nil, err
|
2015-05-04 20:21:03 +00:00
|
|
|
}
|
2015-04-14 15:12:27 +00:00
|
|
|
loc := &url.URL{
|
|
|
|
Scheme: nodeScheme,
|
|
|
|
Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
|
2015-11-27 18:19:36 +00:00
|
|
|
Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
|
2015-04-14 15:12:27 +00:00
|
|
|
RawQuery: params.Encode(),
|
|
|
|
}
|
|
|
|
return loc, nodeTransport, nil
|
|
|
|
}
|
|
|
|
|
2015-09-09 03:40:36 +00:00
|
|
|
// PortForwardLocation returns the port-forward URL for a pod.
|
2015-10-27 13:18:45 +00:00
|
|
|
func PortForwardLocation(
|
|
|
|
getter ResourceGetter,
|
|
|
|
connInfo client.ConnectionInfoGetter,
|
|
|
|
ctx api.Context,
|
|
|
|
name string,
|
|
|
|
) (*url.URL, http.RoundTripper, error) {
|
2015-04-14 15:12:27 +00:00
|
|
|
pod, err := getPod(getter, ctx, name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2015-05-22 23:40:57 +00:00
|
|
|
nodeHost := pod.Spec.NodeName
|
2015-04-14 15:12:27 +00:00
|
|
|
if len(nodeHost) == 0 {
|
|
|
|
// If pod has not been assigned a host, return an empty location
|
|
|
|
return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
|
|
|
|
}
|
2015-10-27 13:18:45 +00:00
|
|
|
nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
|
2015-04-14 15:12:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
loc := &url.URL{
|
|
|
|
Scheme: nodeScheme,
|
|
|
|
Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
|
2015-11-27 18:19:36 +00:00
|
|
|
Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name),
|
2015-04-14 15:12:27 +00:00
|
|
|
}
|
|
|
|
return loc, nodeTransport, nil
|
|
|
|
}
|