// listCollection will list the items in the specified namespace
// it returns the following:
//  the list of items in the collection (if found)
//  a boolean if the operation is supported
//  an error if the operation is supported but could not be completed.
func listCollection(
	dynamicClient *dynamic.Client,
	opCache operationNotSupportedCache,
	gvr unversioned.GroupVersionResource,
	namespace string,
) (*runtime.UnstructuredList, bool, error) {
	glog.V(5).Infof("namespace controller - listCollection - namespace: %s, gvr: %v", namespace, gvr)

	key := operationKey{op: operationList, gvr: gvr}
	if !opCache.isSupported(key) {
		glog.V(5).Infof("namespace controller - listCollection ignored since not supported - namespace: %s, gvr: %v", namespace, gvr)
		return nil, false, nil
	}

	apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true}
	unstructuredList, err := dynamicClient.Resource(&apiResource, namespace).List(v1.ListOptions{})
	if err == nil {
		return unstructuredList, true, nil
	}

	// this is strange, but we need to special case for both MethodNotSupported and NotFound errors
	// TODO: https://github.com/kubernetes/kubernetes/issues/22413
	// we have a resource returned in the discovery API that supports no top-level verbs:
	//  /apis/extensions/v1beta1/namespaces/default/replicationcontrollers
	// when working with this resource type, we will get a literal not found error rather than expected method not supported
	// remember next time that this resource does not support delete collection...
	if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) {
		glog.V(5).Infof("namespace controller - listCollection not supported - namespace: %s, gvr: %v", namespace, gvr)
		opCache[key] = true
		return nil, false, nil
	}

	return nil, true, err
}
Beispiel #2
0
// DefaultRetriable adds retry information to the provided error, and will refresh the
// info if the client info is stale. If the refresh fails the error is made fatal.
// All other errors are left in their natural state - they will not be retried unless
// they define a Temporary() method that returns true.
func DefaultRetriable(info *resource.Info, err error) error {
	if err == nil {
		return nil
	}
	switch {
	case errors.IsMethodNotSupported(err):
		return ErrNotRetriable{err}
	case errors.IsConflict(err):
		if refreshErr := info.Get(); refreshErr != nil {
			return ErrNotRetriable{err}
		}
		return ErrRetriable{err}
	case errors.IsServerTimeout(err):
		return ErrRetriable{err}
	}
	return err
}
// deleteCollection is a helper function that will delete the collection of resources
// it returns true if the operation was supported on the server.
// it returns an error if the operation was supported on the server but was unable to complete.
func deleteCollection(
	dynamicClient *dynamic.Client,
	opCache operationNotSupportedCache,
	gvr unversioned.GroupVersionResource,
	namespace string,
) (bool, error) {
	glog.V(5).Infof("namespace controller - deleteCollection - namespace: %s, gvr: %v", namespace, gvr)

	key := operationKey{op: operationDeleteCollection, gvr: gvr}
	if !opCache.isSupported(key) {
		glog.V(5).Infof("namespace controller - deleteCollection ignored since not supported - namespace: %s, gvr: %v", namespace, gvr)
		return false, nil
	}

	apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true}

	// namespace controller does not want the garbage collector to insert the orphan finalizer since it calls
	// resource deletions generically.  it will ensure all resources in the namespace are purged prior to releasing
	// namespace itself.
	orphanDependents := false
	err := dynamicClient.Resource(&apiResource, namespace).DeleteCollection(&v1.DeleteOptions{OrphanDependents: &orphanDependents}, &v1.ListOptions{})

	if err == nil {
		return true, nil
	}

	// this is strange, but we need to special case for both MethodNotSupported and NotFound errors
	// TODO: https://github.com/kubernetes/kubernetes/issues/22413
	// we have a resource returned in the discovery API that supports no top-level verbs:
	//  /apis/extensions/v1beta1/namespaces/default/replicationcontrollers
	// when working with this resource type, we will get a literal not found error rather than expected method not supported
	// remember next time that this resource does not support delete collection...
	if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) {
		glog.V(5).Infof("namespace controller - deleteCollection not supported - namespace: %s, gvr: %v", namespace, gvr)
		opCache[key] = true
		return false, nil
	}

	glog.V(5).Infof("namespace controller - deleteCollection unexpected error - namespace: %s, gvr: %v, error: %v", namespace, gvr, err)
	return true, err
}
// deleteEachItem is a helper function that will list the collection of resources and delete each item 1 by 1.
func deleteEachItem(
	dynamicClient *dynamic.Client,
	opCache operationNotSupportedCache,
	gvr unversioned.GroupVersionResource,
	namespace string,
) error {
	glog.V(5).Infof("namespace controller - deleteEachItem - namespace: %s, gvr: %v", namespace, gvr)

	unstructuredList, listSupported, err := listCollection(dynamicClient, opCache, gvr, namespace)
	if err != nil {
		return err
	}
	if !listSupported {
		return nil
	}
	apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true}
	for _, item := range unstructuredList.Items {
		if err = dynamicClient.Resource(&apiResource, namespace).Delete(item.Name, nil); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) {
			return err
		}
	}
	return nil
}
Beispiel #5
0
func RunPortForward(f *cmdutil.Factory, cmd *cobra.Command, args []string, fw portForwarder) error {
	podName := cmdutil.GetFlagString(cmd, "pod")
	if len(podName) == 0 && len(args) == 0 {
		return cmdutil.UsageError(cmd, "POD is required for port-forward")
	}

	if len(podName) != 0 {
		printDeprecationWarning("port-forward POD", "-p POD")
	} else {
		podName = args[0]
		args = args[1:]
	}

	if len(args) < 1 {
		return cmdutil.UsageError(cmd, "at least 1 PORT is required for port-forward")
	}

	namespace, _, err := f.DefaultNamespace()
	if err != nil {
		return err
	}

	client, err := f.Client()
	if err != nil {
		return err
	}

	pod, err := client.Pods(namespace).Get(podName)
	if err != nil {
		return err
	}

	if pod.Status.Phase != api.PodRunning {
		glog.Fatalf("Unable to execute command because pod is not running. Current status=%v", pod.Status.Phase)
	}

	config, err := f.ClientConfig()
	if err != nil {
		return err
	}

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt)
	defer signal.Stop(signals)

	stopCh := make(chan struct{}, 1)
	go func() {
		<-signals
		close(stopCh)
	}()

	req := client.RESTClient.Post().
		Resource("pods").
		Namespace(namespace).
		Name(pod.Name).
		SubResource("portforward")

	postErr := fw.ForwardPorts("POST", req.URL(), config, args, stopCh)

	// if we don't have an error, return.  If we did get an error, try a GET because v3.0.0 shipped with port-forward running as a GET.
	if postErr == nil {
		return nil
	}
	// only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help
	if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) {
		return postErr
	}

	getReq := client.RESTClient.Get().
		Resource("pods").
		Namespace(namespace).
		Name(pod.Name).
		SubResource("portforward")
	getErr := fw.ForwardPorts("GET", getReq.URL(), config, args, stopCh)
	if getErr == nil {
		return nil
	}

	// if we got a getErr, return the postErr because it's more likely to be correct.  GET is legacy
	return postErr
}
Beispiel #6
0
// Run executes a validated remote execution against a pod.
func (p *ExecOptions) Run() error {
	pod, err := p.Client.Pods(p.Namespace).Get(p.PodName)
	if err != nil {
		return err
	}

	containerName := p.ContainerName
	if len(containerName) == 0 {
		if len(pod.Spec.Containers) > 1 {
			fmt.Fprintf(p.Err, "defaulting container name to %s, use '%s describe po/%s' cmd to see all containers in this pod", pod.Spec.Containers[0].Name, p.FullCmdName, p.PodName)
		}
		containerName = pod.Spec.Containers[0].Name
	}

	// ensure we can recover the terminal while attached
	t := p.setupTTY()

	var sizeQueue term.TerminalSizeQueue
	if t.Raw {
		// this call spawns a goroutine to monitor/update the terminal size
		sizeQueue = t.MonitorSize(t.GetSize())

		// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
		// true
		p.Err = nil
	}

	fn := func() error {
		// TODO: consider abstracting into a client invocation or client helper
		req := p.Client.RESTClient.Post().
			Resource("pods").
			Name(pod.Name).
			Namespace(pod.Namespace).
			SubResource("exec").
			Param("container", containerName)
		req.VersionedParams(&api.PodExecOptions{
			Container: containerName,
			Command:   p.Command,
			Stdin:     p.Stdin,
			Stdout:    p.Out != nil,
			Stderr:    p.Err != nil,
			TTY:       t.Raw,
		}, api.ParameterCodec)

		postErr := p.Executor.Execute("POST", req.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue)

		// if we don't have an error, return.  If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET.
		if postErr == nil {
			return nil
		}
		// only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help
		if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) {
			return postErr
		}

		getReq := p.Client.RESTClient.Get().
			Resource("pods").
			Name(pod.Name).
			Namespace(pod.Namespace).
			SubResource("exec").
			Param("container", containerName)
		getReq.VersionedParams(&api.PodExecOptions{
			Container: containerName,
			Command:   p.Command,
			Stdin:     p.Stdin,
			Stdout:    p.Out != nil,
			Stderr:    p.Err != nil,
			TTY:       t.Raw,
		}, api.ParameterCodec)

		getErr := p.Executor.Execute("GET", getReq.URL(), p.Config, p.In, p.Out, p.Err, t.Raw, sizeQueue)
		if getErr == nil {
			return nil
		}

		// if we got a getErr, return the postErr because it's more likely to be correct.  GET is legacy
		return postErr
	}

	if err := t.Safe(fn); err != nil {
		return err
	}

	return nil
}
Beispiel #7
0
// RunTag contains all the necessary functionality for the OpenShift cli tag command.
func (o TagOptions) RunTag() error {
	for i, destNameAndTag := range o.destNameAndTag {
		destName, destTag, ok := imageapi.SplitImageStreamTag(destNameAndTag)
		if !ok {
			return fmt.Errorf("%q must be of the form <stream_name>:<tag>", destNameAndTag)
		}

		err := kclient.RetryOnConflict(kclient.DefaultRetry, func() error {
			isc := o.osClient.ImageStreams(o.destNamespace[i])

			if o.deleteTag {
				// new server support
				err := o.osClient.ImageStreamTags(o.destNamespace[i]).Delete(destName, destTag)
				switch {
				case err == nil:
					fmt.Fprintf(o.out, "Deleted tag %s/%s.", o.destNamespace[i], destNameAndTag)
					return nil

				case kerrors.IsMethodNotSupported(err), kerrors.IsForbidden(err):
					// fall back to legacy behavior
				default:
					//  error that isn't whitelisted: fail
					return err
				}

				// try the old way
				target, err := isc.Get(destName)
				if err != nil {
					if !kerrors.IsNotFound(err) {
						return err
					}

					// Nothing to do here, continue to the next dest tag
					// if there is any.
					fmt.Fprintf(o.out, "Image stream %q does not exist.\n", destName)
					return nil
				}

				// The user wants to delete a spec tag.
				if _, ok := target.Spec.Tags[destTag]; !ok {
					return fmt.Errorf("destination tag %s/%s does not exist.\n", o.destNamespace[i], destNameAndTag)
				}
				delete(target.Spec.Tags, destTag)

				if _, err = isc.Update(target); err != nil {
					return err
				}

				fmt.Fprintf(o.out, "Deleted tag %s/%s.", o.destNamespace[i], destNameAndTag)
				return nil
			}

			// The user wants to symlink a tag.
			istag := &imageapi.ImageStreamTag{
				ObjectMeta: kapi.ObjectMeta{
					Name:      destNameAndTag,
					Namespace: o.destNamespace[i],
				},
				Tag: &imageapi.TagReference{
					Reference: o.referenceTag,
					ImportPolicy: imageapi.TagImportPolicy{
						Insecure:  o.insecureTag,
						Scheduled: o.scheduleTag,
					},
					From: &kapi.ObjectReference{
						Kind: o.sourceKind,
					},
				},
			}
			localRef := o.ref
			switch o.sourceKind {
			case "DockerImage":
				istag.Tag.From.Name = localRef.Exact()
				gen := int64(0)
				istag.Tag.Generation = &gen

			default:
				istag.Tag.From.Name = localRef.NameString()
				istag.Tag.From.Namespace = o.ref.Namespace
				if len(o.ref.Namespace) == 0 && o.destNamespace[i] != o.namespace {
					istag.Tag.From.Namespace = o.namespace
				}
			}

			msg := ""
			sameNamespace := o.namespace == o.destNamespace[i]
			if o.aliasTag {
				if sameNamespace {
					msg = fmt.Sprintf("Tag %s set up to track %s.", destNameAndTag, o.ref.Exact())
				} else {
					msg = fmt.Sprintf("Tag %s/%s set up to track %s.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
				}
			} else {
				if istag.Tag.ImportPolicy.Scheduled {
					if sameNamespace {
						msg = fmt.Sprintf("Tag %s set to import %s periodically.", destNameAndTag, o.ref.Exact())
					} else {
						msg = fmt.Sprintf("Tag %s/%s set to %s periodically.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
					}
				} else {
					if sameNamespace {
						msg = fmt.Sprintf("Tag %s set to %s.", destNameAndTag, o.ref.Exact())
					} else {
						msg = fmt.Sprintf("Tag %s/%s set to %s.", o.destNamespace[i], destNameAndTag, o.ref.Exact())
					}
				}
			}

			// supported by new servers.
			_, err := o.osClient.ImageStreamTags(o.destNamespace[i]).Update(istag)
			switch {
			case err == nil:
				fmt.Fprintln(o.out, msg)
				return nil

			case kerrors.IsMethodNotSupported(err), kerrors.IsForbidden(err), kerrors.IsNotFound(err):
				// if we got one of these errors, it possible that a Create will do what we need.  Try that
				_, err := o.osClient.ImageStreamTags(o.destNamespace[i]).Create(istag)
				switch {
				case err == nil:
					fmt.Fprintln(o.out, msg)
					return nil

				case kerrors.IsMethodNotSupported(err), kerrors.IsForbidden(err):
					// fall back to legacy behavior
				default:
					//  error that isn't whitelisted: fail
					return err
				}

			default:
				//  error that isn't whitelisted: fail
				return err

			}

			target, err := isc.Get(destName)
			if kerrors.IsNotFound(err) {
				target = &imageapi.ImageStream{
					ObjectMeta: kapi.ObjectMeta{
						Name: destName,
					},
				}
			} else if err != nil {
				return err
			}

			if target.Spec.Tags == nil {
				target.Spec.Tags = make(map[string]imageapi.TagReference)
			}

			if oldTargetTag, exists := target.Spec.Tags[destTag]; exists {
				if oldTargetTag.Generation == nil {
					// for servers that do not support tag generations, we need to force re-import to fetch its metadata
					delete(target.Annotations, imageapi.DockerImageRepositoryCheckAnnotation)
					istag.Tag.Generation = nil
				}
			}
			target.Spec.Tags[destTag] = *istag.Tag

			// Check the stream creation timestamp and make sure we will not
			// create a new image stream while deleting.
			if target.CreationTimestamp.IsZero() && !o.deleteTag {
				_, err = isc.Create(target)
			} else {
				_, err = isc.Update(target)
			}
			if err != nil {
				return err
			}

			fmt.Fprintln(o.out, msg)
			return nil
		})
		if err != nil {
			return err
		}
	}

	return nil
}
Beispiel #8
0
// Run executes a validated remote execution against a pod.
func (p *ExecOptions) Run() error {
	pod, err := p.Client.Pods(p.Namespace).Get(p.PodName)
	if err != nil {
		return err
	}

	if pod.Status.Phase != api.PodRunning {
		return fmt.Errorf("pod %s is not running and cannot execute commands; current phase is %s", p.PodName, pod.Status.Phase)
	}

	containerName := p.ContainerName
	if len(containerName) == 0 {
		glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
		containerName = pod.Spec.Containers[0].Name
	}

	// TODO: refactor with terminal helpers from the edit utility once that is merged
	var stdin io.Reader
	tty := p.TTY
	if p.Stdin {
		stdin = p.In
		if tty {
			if file, ok := stdin.(*os.File); ok {
				inFd := file.Fd()
				if term.IsTerminal(inFd) {
					oldState, err := term.SetRawTerminal(inFd)
					if err != nil {
						glog.Fatal(err)
					}
					// this handles a clean exit, where the command finished
					defer term.RestoreTerminal(inFd, oldState)

					// SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens
					// for SIGINT and restores the terminal before exiting)

					// this handles SIGTERM
					sigChan := make(chan os.Signal, 1)
					signal.Notify(sigChan, syscall.SIGTERM)
					go func() {
						<-sigChan
						term.RestoreTerminal(inFd, oldState)
						os.Exit(0)
					}()
				} else {
					fmt.Fprintln(p.Err, "STDIN is not a terminal")
				}
			} else {
				tty = false
				fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file")
			}
		}
	}

	// TODO: consider abstracting into a client invocation or client helper
	req := p.Client.RESTClient.Post().
		Resource("pods").
		Name(pod.Name).
		Namespace(pod.Namespace).
		SubResource("exec").
		Param("container", containerName)

	postErr := p.Executor.Execute(req, p.Config, p.Command, stdin, p.Out, p.Err, tty)

	// if we don't have an error, return.  If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET.
	if postErr == nil {
		return nil
	}

	// only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help
	if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) {
		return postErr
	}

	getReq := p.Client.RESTClient.Get().
		Resource("pods").
		Name(pod.Name).
		Namespace(pod.Namespace).
		SubResource("exec").
		Param("container", containerName)

	getErr := p.Executor.Execute(getReq, p.Config, p.Command, stdin, p.Out, p.Err, tty)
	if getErr == nil {
		return nil
	}

	// if we got a getErr, return the postErr because it's more likely to be correct.  GET is legacy
	return postErr
}