diff --git a/docs/cmd/kn_revision_delete.md b/docs/cmd/kn_revision_delete.md index 1a46f6582..0905f7364 100644 --- a/docs/cmd/kn_revision_delete.md +++ b/docs/cmd/kn_revision_delete.md @@ -21,8 +21,11 @@ kn revision delete NAME [flags] ### Options ``` + --async DEPRECATED: please use --no-wait instead. Delete revision and don't wait for it to be deleted. -h, --help help for delete -n, --namespace string Specify the namespace to operate in. + --no-wait Delete revision and don't wait for it to be deleted. + --wait-timeout int Seconds to wait before giving up on waiting for revision to be deleted. (default 600) ``` ### Options inherited from parent commands diff --git a/pkg/kn/commands/revision/delete.go b/pkg/kn/commands/revision/delete.go index 3a856004d..109448e8a 100644 --- a/pkg/kn/commands/revision/delete.go +++ b/pkg/kn/commands/revision/delete.go @@ -17,6 +17,7 @@ package revision import ( "errors" "fmt" + "time" "github.com/spf13/cobra" @@ -25,6 +26,8 @@ import ( // NewRevisionDeleteCommand represent 'revision delete' command func NewRevisionDeleteCommand(p *commands.KnParams) *cobra.Command { + var waitFlags commands.WaitFlags + RevisionDeleteCommand := &cobra.Command{ Use: "delete NAME", Short: "Delete a revision.", @@ -45,7 +48,11 @@ func NewRevisionDeleteCommand(p *commands.KnParams) *cobra.Command { } for _, name := range args { - err = client.DeleteRevision(name) + timeout := time.Duration(0) + if !waitFlags.NoWait { + timeout = time.Duration(waitFlags.TimeoutInSeconds) * time.Second + } + err = client.DeleteRevision(name, timeout) if err != nil { fmt.Fprintf(cmd.OutOrStdout(), "%s.\n", err) } else { @@ -56,5 +63,6 @@ func NewRevisionDeleteCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(RevisionDeleteCommand.Flags(), false) + waitFlags.AddConditionWaitFlags(RevisionDeleteCommand, commands.WaitDefaultTimeout, "Delete", "revision", "deleted") return RevisionDeleteCommand } diff --git a/pkg/kn/commands/revision/delete_test.go b/pkg/kn/commands/revision/delete_test.go index 53ac70ddc..bc833e96e 100644 --- a/pkg/kn/commands/revision/delete_test.go +++ b/pkg/kn/commands/revision/delete_test.go @@ -15,14 +15,19 @@ package revision import ( + "errors" "testing" "gotest.tools/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" clienttesting "k8s.io/client-go/testing" "knative.dev/client/pkg/kn/commands" "knative.dev/client/pkg/util" + "knative.dev/client/pkg/wait" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) func fakeRevisionDelete(args []string) (action clienttesting.Action, name string, output string, err error) { @@ -35,6 +40,17 @@ func fakeRevisionDelete(args []string) (action clienttesting.Action, name string name = deleteAction.GetName() return true, nil, nil }) + fakeServing.AddWatchReactor("revisions", + func(a clienttesting.Action) (bool, watch.Interface, error) { + watchAction := a.(clienttesting.WatchAction) + _, found := watchAction.GetWatchRestrictions().Fields.RequiresExactMatch("metadata.name") + if !found { + return true, nil, errors.New("no field selector on metadata.name found") + } + w := wait.NewFakeWatch(getRevisionDeleteEvents("test-revision")) + w.Start() + return true, w, nil + }) cmd.SetArgs(args) err = cmd.Execute() if err != nil { @@ -79,3 +95,11 @@ func TestMultipleRevisionDelete(t *testing.T) { assert.Check(t, util.ContainsAll(output, "Revision", revName2, "deleted", "namespace", commands.FakeNamespace)) assert.Check(t, util.ContainsAll(output, "Revision", revName3, "deleted", "namespace", commands.FakeNamespace)) } + +func getRevisionDeleteEvents(name string) []watch.Event { + return []watch.Event{ + {watch.Added, &servingv1.Revision{ObjectMeta: metav1.ObjectMeta{Name: name}}}, + {watch.Modified, &servingv1.Revision{ObjectMeta: metav1.ObjectMeta{Name: name}}}, + {watch.Deleted, &servingv1.Revision{ObjectMeta: metav1.ObjectMeta{Name: name}}}, + } +} diff --git a/pkg/serving/v1/client.go b/pkg/serving/v1/client.go index 1a6c1defe..003199be2 100644 --- a/pkg/serving/v1/client.go +++ b/pkg/serving/v1/client.go @@ -89,7 +89,7 @@ type KnServingClient interface { ListRevisions(opts ...ListConfig) (*servingv1.RevisionList, error) // Delete a revision - DeleteRevision(name string) error + DeleteRevision(name string, timeout time.Duration) error // Get a route by its unique name GetRoute(name string) (*servingv1.Route, error) @@ -177,6 +177,11 @@ func (cl *knServingClient) WatchService(name string, timeout time.Duration) (wat cl.client.RESTClient(), cl.namespace, "services", name, timeout) } +func (cl *knServingClient) WatchRevision(name string, timeout time.Duration) (watch.Interface, error) { + return wait.NewWatcher(cl.client.Revisions(cl.namespace).Watch, + cl.client.RESTClient(), cl.namespace, "revision", name, timeout) +} + // List services func (cl *knServingClient) ListServices(config ...ListConfig) (*servingv1.ServiceList, error) { serviceList, err := cl.client.Services(cl.namespace).List(ListConfigs(config).toListOptions()) @@ -370,7 +375,25 @@ func getBaseRevision(cl KnServingClient, service *servingv1.Service) (*servingv1 } // Delete a revision by name -func (cl *knServingClient) DeleteRevision(name string) error { +func (cl *knServingClient) DeleteRevision(name string, timeout time.Duration) error { + if timeout == 0 { + return cl.deleteRevision(name) + } + waitC := make(chan error) + go func() { + waitForEvent := wait.NewWaitForEvent("revision", cl.WatchRevision, func(evt *watch.Event) bool { return evt.Type == watch.Deleted }) + err, _ := waitForEvent.Wait(name, timeout, wait.NoopMessageCallback()) + waitC <- err + }() + err := cl.deleteRevision(name) + if err != nil { + return clienterrors.GetError(err) + } + + return <-waitC +} + +func (cl *knServingClient) deleteRevision(name string) error { err := cl.client.Revisions(cl.namespace).Delete(name, &v1.DeleteOptions{}) if err != nil { return clienterrors.GetError(err) diff --git a/pkg/serving/v1/client_mock.go b/pkg/serving/v1/client_mock.go index 9381ed34c..c60583e7a 100644 --- a/pkg/serving/v1/client_mock.go +++ b/pkg/serving/v1/client_mock.go @@ -146,12 +146,12 @@ func (c *MockKnServingClient) ListRevisions(opts ...ListConfig) (*servingv1.Revi } // Delete a revision -func (sr *ServingRecorder) DeleteRevision(name interface{}, err error) { - sr.r.Add("DeleteRevision", []interface{}{name}, []interface{}{err}) +func (sr *ServingRecorder) DeleteRevision(name, timeout interface{}, err error) { + sr.r.Add("DeleteRevision", []interface{}{name, timeout}, []interface{}{err}) } -func (c *MockKnServingClient) DeleteRevision(name string) error { - call := c.recorder.r.VerifyCall("DeleteRevision", name) +func (c *MockKnServingClient) DeleteRevision(name string, timeout time.Duration) error { + call := c.recorder.r.VerifyCall("DeleteRevision", name, timeout) return mock.ErrorOrNil(call.Result[0]) } diff --git a/pkg/serving/v1/client_mock_test.go b/pkg/serving/v1/client_mock_test.go index 4bf80e524..ec85dd76c 100644 --- a/pkg/serving/v1/client_mock_test.go +++ b/pkg/serving/v1/client_mock_test.go @@ -40,7 +40,7 @@ func TestMockKnClient(t *testing.T) { recorder.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback(), nil, 10*time.Second) recorder.GetRevision("hello", nil, nil) recorder.ListRevisions(mock.Any(), nil, nil) - recorder.DeleteRevision("hello", nil) + recorder.DeleteRevision("hello", time.Duration(10)*time.Second, nil) recorder.GetRoute("hello", nil, nil) recorder.ListRoutes(mock.Any(), nil, nil) recorder.GetConfiguration("hello", nil, nil) @@ -54,7 +54,7 @@ func TestMockKnClient(t *testing.T) { client.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback()) client.GetRevision("hello") client.ListRevisions(WithName("blub")) - client.DeleteRevision("hello") + client.DeleteRevision("hello", time.Duration(10)*time.Second) client.GetRoute("hello") client.ListRoutes(WithName("blub")) client.GetConfiguration("hello")