// 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 }
// 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 }
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 }
// 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 }
// 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 }
// 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 }