Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubectl --wait flag #63695

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pkg/kubectl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ go_test(
"autoscale_test.go",
"clusterrolebinding_test.go",
"configmap_test.go",
"delete_test.go",
"deployment_test.go",
"env_file_test.go",
"generate_test.go",
Expand All @@ -21,6 +20,7 @@ go_test(
"pdb_test.go",
"priorityclass_test.go",
"quota_test.go",
"reapers_test.go",
"rolebinding_test.go",
"rollback_test.go",
"rolling_updater_test.go",
Expand Down Expand Up @@ -91,7 +91,7 @@ go_library(
"clusterrolebinding.go",
"conditions.go",
"configmap.go",
"delete.go",
"delete_wait.go",
"deployment.go",
"doc.go",
"env_file.go",
Expand All @@ -102,6 +102,7 @@ go_library(
"pdb.go",
"priorityclass.go",
"quota.go",
"reapers.go",
"rolebinding.go",
"rollback.go",
"rolling_updater.go",
Expand Down
52 changes: 37 additions & 15 deletions pkg/kubectl/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
Expand Down Expand Up @@ -90,14 +91,21 @@ var (
type DeleteOptions struct {
resource.FilenameOptions

LabelSelector string
FieldSelector string
DeleteAll bool
IgnoreNotFound bool
Cascade bool
DeleteNow bool
ForceDeletion bool
LabelSelector string
FieldSelector string
DeleteAll bool
IgnoreNotFound bool
Cascade bool
DeleteNow bool
ForceDeletion bool
// Whether we should wait for resource deletion.
// Tied to `--wait` flag.
WaitForDeletion bool
// Whether we should wait for deletion when the resource is deleted gracefully.
// This is to support backwards compatibility when graceful deletion was introduced.
// This option is not exposed to the user.
// Deprecated: Remove this flag once the reapers are replaced with foreground deletion mode
WaitForGracefulDeletion bool

Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error)

Expand Down Expand Up @@ -164,7 +172,7 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1 and wait until the object is successfully deleted. Users may provide --force
// to bypass this wait.
o.WaitForDeletion = true
o.WaitForGracefulDeletion = true
o.GracePeriod = 1
}

Expand Down Expand Up @@ -245,7 +253,8 @@ func (o *DeleteOptions) ReapResult(r *resource.Result, isDefaultDelete, quiet bo
// If there is no reaper for this resources and the user didn't explicitly ask for stop.
if kubectl.IsNoSuchReaperError(err) && isDefaultDelete {
// No client side reaper found. Let the server do cascading deletion.
return o.cascadingDeleteResource(info)
falseVar := false
return o.deleteResourceAndWait(info, &metav1.DeleteOptions{OrphanDependents: &falseVar})
}
return cmdutil.AddSourceToErr("reaping", info.Source, err)
}
Expand All @@ -256,7 +265,7 @@ func (o *DeleteOptions) ReapResult(r *resource.Result, isDefaultDelete, quiet bo
if err := reaper.Stop(info.Namespace, info.Name, o.Timeout, options); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err)
}
if o.WaitForDeletion {
if o.WaitForGracefulDeletion {
if err := waitForObjectDeletion(info, o.Timeout); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err)
}
Expand Down Expand Up @@ -293,7 +302,7 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
}
options.OrphanDependents = &orphan
return o.deleteResource(info, options)
return o.deleteResourceAndWait(info, options)
})
if err != nil {
return err
Expand All @@ -304,13 +313,26 @@ func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
return nil
}

func (o *DeleteOptions) cascadingDeleteResource(info *resource.Info) error {
falseVar := false
return o.deleteResource(info, &metav1.DeleteOptions{OrphanDependents: &falseVar})
// Deletes the given resource and then waits to confirm that the resource is deleted.
func (o *DeleteOptions) deleteResourceAndWait(info *resource.Info, options *metav1.DeleteOptions) error {
if !o.WaitForDeletion {
return o.deleteResource(info, options)
}

deleteFunc := func(options *metav1.DeleteOptions) (runtime.Object, error) {
return resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, options)
}
err := kubectl.WaitForDeletion(deleteFunc, options, timeout)
if err != nil {
return err
}

o.PrintObj(info)
return nil
}

func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
if _, err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
return cmdutil.AddSourceToErr("deleting", info.Source, err)
}

Expand Down
11 changes: 11 additions & 0 deletions pkg/kubectl/cmd/delete_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type DeleteFlags struct {
All *bool
Cascade *bool
Force *bool
Wait *bool
GracePeriod *int
IgnoreNotFound *bool
Now *bool
Expand Down Expand Up @@ -102,6 +103,9 @@ func (f *DeleteFlags) ToOptions(streams genericclioptions.IOStreams) *DeleteOpti
if f.Force != nil {
options.ForceDeletion = *f.Force
}
if f.Wait != nil {
options.WaitForDeletion = *f.Wait
}
if f.GracePeriod != nil {
options.GracePeriod = *f.GracePeriod
}
Expand Down Expand Up @@ -132,6 +136,9 @@ func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
if f.Force != nil {
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
}
if f.Wait != nil {
cmd.Flags().BoolVar(f.Wait, "wait", *f.Wait, "If true, waits for the resource to be deleted before returning.")
}
if f.Cascade != nil {
cmd.Flags().BoolVar(f.Cascade, "cascade", *f.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
}
Expand Down Expand Up @@ -162,6 +169,7 @@ func NewDeleteCommandFlags(usage string) *DeleteFlags {
// setup command defaults
all := false
force := false
wait := true
ignoreNotFound := false
now := false
output := ""
Expand All @@ -182,6 +190,7 @@ func NewDeleteCommandFlags(usage string) *DeleteFlags {

All: &all,
Force: &force,
Wait: &wait,
IgnoreNotFound: &ignoreNotFound,
Now: &now,
Timeout: &timeout,
Expand All @@ -195,6 +204,7 @@ func NewDeleteFlags(usage string) *DeleteFlags {
gracePeriod := -1

force := false
wait := false
timeout := time.Duration(0)

filenames := []string{}
Expand All @@ -208,6 +218,7 @@ func NewDeleteFlags(usage string) *DeleteFlags {

// add non-defaults
Force: &force,
Wait: &wait,
Timeout: &timeout,
}
}
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1 and wait until the object is successfully deleted.
deleteOpts.GracePeriod = 1
deleteOpts.WaitForDeletion = true
deleteOpts.WaitForGracefulDeletion = true
}
o.DeleteOptions = deleteOpts

Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {

deleteOpts := o.DeleteFlags.ToOptions(o.IOStreams)
deleteOpts.IgnoreNotFound = true
deleteOpts.WaitForDeletion = false
deleteOpts.WaitForGracefulDeletion = false
deleteOpts.GracePeriod = -1
deleteOpts.Reaper = f.Reaper

Expand Down
76 changes: 76 additions & 0 deletions pkg/kubectl/delete_wait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2018 The Kubernetes Authors.

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.
*/

package kubectl

import (
"fmt"
"time"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)

type deleteFunc func(options *metav1.DeleteOptions) (runtime.Object, error)

// Sends a request to DELETE the object and waits till the object is deleted.
// deleteFn should return the deleted object or a metav1.Status object.
func WaitForDeletion(deleteFn deleteFunc, options *metav1.DeleteOptions, timeout time.Duration) error {
obj, err := deleteFn(options)
if err != nil {
return err
}
// Keep deleting the resource with uid precondition until we get a
// resource not found error.
// We use DELETE here instead of using GET to ensure that DELETE
// works without requiring GET permissions.
uid, err := getUID(obj)
if err != nil {
return fmt.Errorf("unexpected error in extracting uid: %s", err)
}
options.Preconditions = metav1.NewUIDPreconditions(uid)
err = wait.PollImmediate(time.Second, timeout, func() (bool, error) {
_, err := deleteFn(options)
if errors.IsNotFound(err) || errors.IsConflict(err) {
// Resource successfully deleted.
return true, nil
}
return false, err
})
return err
}

func getUID(obj runtime.Object) (string, error) {
// Check if the object is of type Status.
status, isStatus := obj.(*metav1.Status)
if isStatus {
if status.Details == nil || status.Details.UID == "" {
// This can happen for an older apiserver (before 1.7)
// which does not set Details.
return "", fmt.Errorf("UID was not found in the status details: %#v", status)
}
return string(status.Details.UID), nil
}
// The resource is itself returned.
accessor, err := meta.Accessor(obj)
if err != nil {
return "", err
}
return string(accessor.GetUID()), nil
}
7 changes: 4 additions & 3 deletions pkg/kubectl/genericclioptions/resource/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,18 @@ func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Int
}

func (m *Helper) Delete(namespace, name string) error {
return m.DeleteWithOptions(namespace, name, nil)
_, err := m.DeleteWithOptions(namespace, name, nil)
return err
}

func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) error {
func (m *Helper) DeleteWithOptions(namespace, name string, options *metav1.DeleteOptions) (runtime.Object, error) {
return m.RESTClient.Delete().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
Body(options).
Do().
Error()
Get()
}

func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
Expand Down
File renamed without changes.
File renamed without changes.