delete.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package delete
  14. import (
  15. "fmt"
  16. "strings"
  17. "time"
  18. "github.com/spf13/cobra"
  19. "k8s.io/klog"
  20. "k8s.io/apimachinery/pkg/api/errors"
  21. "k8s.io/apimachinery/pkg/api/meta"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/cli-runtime/pkg/genericclioptions"
  25. "k8s.io/cli-runtime/pkg/printers"
  26. "k8s.io/cli-runtime/pkg/resource"
  27. "k8s.io/client-go/dynamic"
  28. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  29. cmdwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
  30. "k8s.io/kubernetes/pkg/kubectl/util/i18n"
  31. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  32. )
  33. var (
  34. deleteLong = templates.LongDesc(i18n.T(`
  35. Delete resources by filenames, stdin, resources and names, or by resources and label selector.
  36. JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
  37. resources and names, or resources and label selector.
  38. Some resources, such as pods, support graceful deletion. These resources define a default period
  39. before they are forcibly terminated (the grace period) but you may override that value with
  40. the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
  41. represent entities in the cluster, deletion may not be acknowledged immediately. If the node
  42. hosting a pod is down or cannot reach the API server, termination may take significantly longer
  43. than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
  44. the --force flag.
  45. IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
  46. terminated, which can leave those processes running until the node detects the deletion and
  47. completes graceful deletion. If your processes use shared storage or talk to a remote API and
  48. depend on the name of the pod to identify themselves, force deleting those pods may result in
  49. multiple processes running on different machines using the same identification which may lead
  50. to data corruption or inconsistency. Only force delete pods when you are sure the pod is
  51. terminated, or if your application can tolerate multiple copies of the same pod running at once.
  52. Also, if you force delete pods the scheduler may place new pods on those nodes before the node
  53. has released those resources and causing those pods to be evicted immediately.
  54. Note that the delete command does NOT do resource version checks, so if someone submits an
  55. update to a resource right when you submit a delete, their update will be lost along with the
  56. rest of the resource.`))
  57. deleteExample = templates.Examples(i18n.T(`
  58. # Delete a pod using the type and name specified in pod.json.
  59. kubectl delete -f ./pod.json
  60. # Delete resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml.
  61. kubectl delete -k dir
  62. # Delete a pod based on the type and name in the JSON passed into stdin.
  63. cat pod.json | kubectl delete -f -
  64. # Delete pods and services with same names "baz" and "foo"
  65. kubectl delete pod,service baz foo
  66. # Delete pods and services with label name=myLabel.
  67. kubectl delete pods,services -l name=myLabel
  68. # Delete a pod with minimal delay
  69. kubectl delete pod foo --now
  70. # Force delete a pod on a dead node
  71. kubectl delete pod foo --grace-period=0 --force
  72. # Delete all pods
  73. kubectl delete pods --all`))
  74. )
  75. type DeleteOptions struct {
  76. resource.FilenameOptions
  77. LabelSelector string
  78. FieldSelector string
  79. DeleteAll bool
  80. DeleteAllNamespaces bool
  81. IgnoreNotFound bool
  82. Cascade bool
  83. DeleteNow bool
  84. ForceDeletion bool
  85. WaitForDeletion bool
  86. Quiet bool
  87. WarnClusterScope bool
  88. GracePeriod int
  89. Timeout time.Duration
  90. Output string
  91. DynamicClient dynamic.Interface
  92. Mapper meta.RESTMapper
  93. Result *resource.Result
  94. genericclioptions.IOStreams
  95. }
  96. func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
  97. deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
  98. cmd := &cobra.Command{
  99. Use: "delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)])",
  100. DisableFlagsInUseLine: true,
  101. Short: i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
  102. Long: deleteLong,
  103. Example: deleteExample,
  104. Run: func(cmd *cobra.Command, args []string) {
  105. o := deleteFlags.ToOptions(nil, streams)
  106. cmdutil.CheckErr(o.Complete(f, args, cmd))
  107. cmdutil.CheckErr(o.Validate())
  108. cmdutil.CheckErr(o.RunDelete())
  109. },
  110. SuggestFor: []string{"rm"},
  111. }
  112. deleteFlags.AddFlags(cmd)
  113. cmdutil.AddIncludeUninitializedFlag(cmd)
  114. return cmd
  115. }
  116. func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
  117. cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
  118. if err != nil {
  119. return err
  120. }
  121. o.WarnClusterScope = enforceNamespace && !o.DeleteAllNamespaces
  122. if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
  123. if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
  124. // If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
  125. o.IgnoreNotFound = true
  126. }
  127. }
  128. if o.DeleteNow {
  129. if o.GracePeriod != -1 {
  130. return fmt.Errorf("--now and --grace-period cannot be specified together")
  131. }
  132. o.GracePeriod = 1
  133. }
  134. if o.GracePeriod == 0 && !o.ForceDeletion {
  135. // To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
  136. // into --grace-period=1. Users may provide --force to bypass this conversion.
  137. o.GracePeriod = 1
  138. }
  139. r := f.NewBuilder().
  140. Unstructured().
  141. ContinueOnError().
  142. NamespaceParam(cmdNamespace).DefaultNamespace().
  143. FilenameParam(enforceNamespace, &o.FilenameOptions).
  144. LabelSelectorParam(o.LabelSelector).
  145. FieldSelectorParam(o.FieldSelector).
  146. SelectAllParam(o.DeleteAll).
  147. AllNamespaces(o.DeleteAllNamespaces).
  148. ResourceTypeOrNameArgs(false, args...).RequireObject(false).
  149. Flatten().
  150. Do()
  151. err = r.Err()
  152. if err != nil {
  153. return err
  154. }
  155. o.Result = r
  156. o.Mapper, err = f.ToRESTMapper()
  157. if err != nil {
  158. return err
  159. }
  160. o.DynamicClient, err = f.DynamicClient()
  161. if err != nil {
  162. return err
  163. }
  164. return nil
  165. }
  166. func (o *DeleteOptions) Validate() error {
  167. if o.Output != "" && o.Output != "name" {
  168. return fmt.Errorf("unexpected -o output mode: %v. We only support '-o name'", o.Output)
  169. }
  170. if o.DeleteAll && len(o.LabelSelector) > 0 {
  171. return fmt.Errorf("cannot set --all and --selector at the same time")
  172. }
  173. if o.DeleteAll && len(o.FieldSelector) > 0 {
  174. return fmt.Errorf("cannot set --all and --field-selector at the same time")
  175. }
  176. switch {
  177. case o.GracePeriod == 0 && o.ForceDeletion:
  178. fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
  179. case o.ForceDeletion:
  180. fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
  181. }
  182. return nil
  183. }
  184. func (o *DeleteOptions) RunDelete() error {
  185. return o.DeleteResult(o.Result)
  186. }
  187. func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
  188. found := 0
  189. if o.IgnoreNotFound {
  190. r = r.IgnoreErrors(errors.IsNotFound)
  191. }
  192. warnClusterScope := o.WarnClusterScope
  193. deletedInfos := []*resource.Info{}
  194. uidMap := cmdwait.UIDMap{}
  195. err := r.Visit(func(info *resource.Info, err error) error {
  196. if err != nil {
  197. return err
  198. }
  199. deletedInfos = append(deletedInfos, info)
  200. found++
  201. options := &metav1.DeleteOptions{}
  202. if o.GracePeriod >= 0 {
  203. options = metav1.NewDeleteOptions(int64(o.GracePeriod))
  204. }
  205. policy := metav1.DeletePropagationBackground
  206. if !o.Cascade {
  207. policy = metav1.DeletePropagationOrphan
  208. }
  209. options.PropagationPolicy = &policy
  210. if warnClusterScope && info.Mapping.Scope.Name() == meta.RESTScopeNameRoot {
  211. fmt.Fprintf(o.ErrOut, "warning: deleting cluster-scoped resources, not scoped to the provided namespace\n")
  212. warnClusterScope = false
  213. }
  214. response, err := o.deleteResource(info, options)
  215. if err != nil {
  216. return err
  217. }
  218. resourceLocation := cmdwait.ResourceLocation{
  219. GroupResource: info.Mapping.Resource.GroupResource(),
  220. Namespace: info.Namespace,
  221. Name: info.Name,
  222. }
  223. if status, ok := response.(*metav1.Status); ok && status.Details != nil {
  224. uidMap[resourceLocation] = status.Details.UID
  225. return nil
  226. }
  227. responseMetadata, err := meta.Accessor(response)
  228. if err != nil {
  229. // we don't have UID, but we didn't fail the delete, next best thing is just skipping the UID
  230. klog.V(1).Info(err)
  231. return nil
  232. }
  233. uidMap[resourceLocation] = responseMetadata.GetUID()
  234. return nil
  235. })
  236. if err != nil {
  237. return err
  238. }
  239. if found == 0 {
  240. fmt.Fprintf(o.Out, "No resources found\n")
  241. return nil
  242. }
  243. if !o.WaitForDeletion {
  244. return nil
  245. }
  246. // if we don't have a dynamic client, we don't want to wait. Eventually when delete is cleaned up, this will likely
  247. // drop out.
  248. if o.DynamicClient == nil {
  249. return nil
  250. }
  251. effectiveTimeout := o.Timeout
  252. if effectiveTimeout == 0 {
  253. // if we requested to wait forever, set it to a week.
  254. effectiveTimeout = 168 * time.Hour
  255. }
  256. waitOptions := cmdwait.WaitOptions{
  257. ResourceFinder: genericclioptions.ResourceFinderForResult(resource.InfoListVisitor(deletedInfos)),
  258. UIDMap: uidMap,
  259. DynamicClient: o.DynamicClient,
  260. Timeout: effectiveTimeout,
  261. Printer: printers.NewDiscardingPrinter(),
  262. ConditionFn: cmdwait.IsDeleted,
  263. IOStreams: o.IOStreams,
  264. }
  265. err = waitOptions.RunWait()
  266. if errors.IsForbidden(err) || errors.IsMethodNotSupported(err) {
  267. // if we're forbidden from waiting, we shouldn't fail.
  268. // if the resource doesn't support a verb we need, we shouldn't fail.
  269. klog.V(1).Info(err)
  270. return nil
  271. }
  272. return err
  273. }
  274. func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) (runtime.Object, error) {
  275. deleteResponse, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions)
  276. if err != nil {
  277. return nil, cmdutil.AddSourceToErr("deleting", info.Source, err)
  278. }
  279. if !o.Quiet {
  280. o.PrintObj(info)
  281. }
  282. return deleteResponse, nil
  283. }
  284. // PrintObj for deleted objects is special because we do not have an object to print.
  285. // This mirrors name printer behavior
  286. func (o *DeleteOptions) PrintObj(info *resource.Info) {
  287. operation := "deleted"
  288. groupKind := info.Mapping.GroupVersionKind
  289. kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
  290. if len(groupKind.Group) == 0 {
  291. kindString = strings.ToLower(groupKind.Kind)
  292. }
  293. if o.GracePeriod == 0 {
  294. operation = "force deleted"
  295. }
  296. if o.Output == "name" {
  297. // -o name: prints resource/name
  298. fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
  299. return
  300. }
  301. // understandable output by default
  302. fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
  303. }