Skip to content
Open
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
21 changes: 9 additions & 12 deletions docs/cmd/tkn_taskrun_delete.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,15 @@ or
### Options

```
--all Delete all TaskRuns in a namespace (default: false)
--allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true)
-f, --force Whether to force deletion (default: false)
-h, --help help for delete
-i, --ignore-running ignore running TaskRun (default true)
--ignore-running-pipelinerun ignore deleting taskruns of a running PipelineRun (default true)
--keep int Keep n most recent number of TaskRuns
--keep-since int When deleting all TaskRuns keep the ones that has been completed since n minutes
-o, --output string Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).
--show-managed-fields If true, keep the managedFields when printing objects in JSON or YAML format.
-t, --task string The name of a Task whose TaskRuns should be deleted (does not delete the task)
--template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--all Delete all TaskRuns in a namespace (default: false)
-f, --force Whether to force deletion (default: false)
-h, --help help for delete
-i, --ignore-running ignore running TaskRun (default true)
--ignore-running-pipelinerun ignore deleting taskruns of a running PipelineRun (default true)
--keep int Keep n most recent number of TaskRuns
--keep-since int When deleting all TaskRuns keep the ones that has been completed since n minutes
-o, --output string Output format. Only "json" is supported
-t, --task string The name of a Task whose TaskRuns should be deleted (does not delete the task)
```

### Options inherited from parent commands
Expand Down
15 changes: 1 addition & 14 deletions docs/man/man1/tkn-taskrun-delete.1
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ Delete TaskRuns in a namespace
\fB\-\-all\fP[=false]
Delete all TaskRuns in a namespace (default: false)

.PP
\fB\-\-allow\-missing\-template\-keys\fP[=true]
If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.

.PP
\fB\-f\fP, \fB\-\-force\fP[=false]
Whether to force deletion (default: false)
Expand All @@ -53,21 +49,12 @@ Delete TaskRuns in a namespace

.PP
\fB\-o\fP, \fB\-\-output\fP=""
Output format. One of: (json, yaml, name, go\-template, go\-template\-file, template, templatefile, jsonpath, jsonpath\-as\-json, jsonpath\-file).

.PP
\fB\-\-show\-managed\-fields\fP[=false]
If true, keep the managedFields when printing objects in JSON or YAML format.
Output format. Only "json" is supported

.PP
\fB\-t\fP, \fB\-\-task\fP=""
The name of a Task whose TaskRuns should be deleted (does not delete the task)

.PP
\fB\-\-template\fP=""
Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [
\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]].


.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
Expand Down
65 changes: 52 additions & 13 deletions pkg/cmd/taskrun/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package taskrun

import (
"encoding/json"
"errors"
"fmt"
"strings"
Expand All @@ -32,7 +33,6 @@ import (
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"go.uber.org/multierr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cliopts "k8s.io/cli-runtime/pkg/genericclioptions"
)

type deleteOptions struct {
Expand Down Expand Up @@ -64,7 +64,6 @@ func trExists(args []string, p cli.Params) ([]string, error) {
func deleteCommand(p cli.Params) *cobra.Command {
opts := &options.DeleteOptions{Resource: "TaskRun", ForceDelete: false, DeleteAllNs: false}
deleteOpts := &deleteOptions{}
f := cliopts.NewPrintFlags("delete")
eg := `Delete TaskRuns with names 'foo' and 'bar' in namespace 'quux':

tkn taskrun delete foo bar -n quux
Expand Down Expand Up @@ -92,6 +91,15 @@ or
Err: cmd.OutOrStderr(),
}

output, err := cmd.LocalFlags().GetString("output")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user passes -o yaml or -o table, the command silently falls through to human-readable text output with no error. Add validation:

if output != "" && output != "json" {
    return fmt.Errorf("unsupported output format %q; supported formats: json", output)
}

if err != nil {
return err
}

if output != "" && output != "json" {
return fmt.Errorf("unsupported output format %q; supported formats: json", output)
Comment on lines +99 to +100
}

if deleteOpts.TaskName != "" {
opts.ParentResource = "Task"
opts.ParentResourceName = deleteOpts.TaskName
Expand Down Expand Up @@ -122,17 +130,21 @@ or
return errs
}

if err := opts.CheckOptions(s, availableTrs, p.Namespace()); err != nil {
checkStreams := s
if output == "json" {
checkStreams = &cli.Stream{In: strings.NewReader("y\n"), Out: &strings.Builder{}, Err: s.Err}
}
if err := opts.CheckOptions(checkStreams, availableTrs, p.Namespace()); err != nil {
return err
}

if err := deleteTaskRuns(s, p, availableTrs, opts); err != nil {
if err := deleteTaskRuns(s, p, availableTrs, opts, output); err != nil {
return err
}
return errs
},
}
f.AddFlags(c)
c.Flags().StringP("output", "o", "", `Output format. Only "json" is supported`)
c.Flags().BoolVarP(&opts.ForceDelete, "force", "f", false, "Whether to force deletion (default: false)")
c.Flags().StringVarP(&deleteOpts.TaskName, "task", "t", "", "The name of a Task whose TaskRuns should be deleted (does not delete the task)")
c.Flags().BoolVarP(&opts.DeleteAllNs, "all", "", false, "Delete all TaskRuns in a namespace (default: false)")
Expand All @@ -144,7 +156,7 @@ or
return c
}

func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options.DeleteOptions) error {
func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options.DeleteOptions, output string) error {
var numberOfDeletedTr, numberOfKeptTr int
cs, err := p.Clients()
if err != nil {
Expand Down Expand Up @@ -177,9 +189,17 @@ func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options
prFinished := ownerPrFinished(cs, *tr)

if !prFinished && opts.ForceDelete {
fmt.Fprintf(s.Out, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
if output == "json" {
fmt.Fprintf(s.Err, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
} else {
fmt.Fprintf(s.Out, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
}
}
if !prFinished && !opts.ForceDelete {
if output == "json" {
return fmt.Errorf("taskrun %s is owned by a running PipelineRun; use --force to delete", tr.Name)
}

fmt.Fprintf(s.Out, "TaskRun(s): %s attached to PipelineRun is still running deleting will restart the completed taskrun. Proceed (y/n): ", tr.Name)
if err := opts.TakeInput(s, ""); err != nil {
continue
Expand Down Expand Up @@ -212,14 +232,33 @@ func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options
})

if opts.Keep > 0 && opts.Keep == len(trToKeep) && len(trToDelete) == 0 {
fmt.Fprintf(s.Out, "Associated %s (%d) for Task:%s is/are equal to keep (%d) \n", opts.Resource, len(trToKeep), opts.ParentResourceName, opts.Keep)
return nil
if output != "json" {
fmt.Fprintf(s.Out, "Associated %s (%d) for Task:%s is/are equal to keep (%d) \n", opts.Resource, len(trToKeep), opts.ParentResourceName, opts.Keep)
return nil
}
} else if opts.Keep > len(trToKeep) {
if output != "json" {
fmt.Fprintf(s.Out, "There is/are only %d %s(s) associated for %s: %s \n", len(trToKeep), opts.Resource, opts.ParentResource, opts.ParentResourceName)
return nil
}
Comment thread
Copilot marked this conversation as resolved.
} else {
d.DeleteRelated([]string{opts.ParentResourceName})
}
if opts.Keep > len(trToKeep) {
fmt.Fprintf(s.Out, "There is/are only %d %s(s) associated for %s: %s \n", len(trToKeep), opts.Resource, opts.ParentResource, opts.ParentResourceName)
return nil
}

if output == "json" {
Comment thread
Debashich marked this conversation as resolved.
deleted := append(d.SuccessfulRelatedDeletes(), d.SuccessfulDeletes()...)
if deleted == nil {
deleted = []string{}
}

result := struct {
Deleted []string `json:"deleted"`
}{
Deleted: deleted,
}
d.DeleteRelated([]string{opts.ParentResourceName})
encodeErr := json.NewEncoder(s.Out).Encode(result)
return multierr.Append(encodeErr, d.Errors())
}

if !opts.DeleteAllNs {
Expand Down
58 changes: 58 additions & 0 deletions pkg/cmd/taskrun/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,55 @@ func TestTaskRunDelete_v1beta1(t *testing.T) {
wantError: false,
want: "All 6 TaskRuns associated with Task \"random\" deleted in namespace \"ns\"\n",
},
{
name: "With JSON output",
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "json"},
dynamic: seeds[14].dynamicClient,
input: seeds[14].pipelineClient,
inputStream: nil,
wantError: false,
want: `{"deleted":["tr0-1"]}
`,
},
{
name: "Unsupported output format",
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "yaml"},
dynamic: seeds[0].dynamicClient,
input: seeds[0].pipelineClient,
inputStream: nil,
wantError: true,
want: "unsupported output format \"yaml\"; supported formats: json",
},
{
name: "With JSON output for multiple TaskRuns",
command: []string{"rm", "tr0-1", "tr0-2", "-n", "ns", "-o", "json"},
dynamic: seeds[16].dynamicClient,
input: seeds[16].pipelineClient,
inputStream: strings.NewReader("y\n"),
wantError: false,
want: `{"deleted":["tr0-1","tr0-2"]}
`,
},
{
name: "Delete with JSON output for --task but keep meets or exceeds existing (no-op)",
command: []string{"rm", "--task", "random", "-n", "ns", "--keep", "10", "-o", "json"},
dynamic: seeds[15].dynamicClient,
input: seeds[15].pipelineClient,
inputStream: nil,
wantError: false,
want: `{"deleted":[]}
`,
},
{
name: "Delete with JSON output for --task",
command: []string{"rm", "--task", "random", "-n", "ns", "-o", "json"},
dynamic: seeds[15].dynamicClient,
input: seeds[15].pipelineClient,
inputStream: nil,
wantError: false,
want: `{"deleted":["tr0-1","tr0-2","tr0-3","tr0-9"]}
`,
},
}

for _, tp := range testParams {
Expand Down Expand Up @@ -1326,6 +1375,15 @@ func Test_TaskRuns_Delete_With_Running_PipelineRun_v1beta1(t *testing.T) {
wantError bool
want string
}{
{
name: "Taskrun with running pipelinerun JSON without force",
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "json"},
dynamic: seeds[0].dynamicClient,
input: seeds[0].pipelineClient,
inputStream: nil,
wantError: true,
want: "taskrun tr0-1 is owned by a running PipelineRun; use --force to delete",
},
{
name: "Taskrun with running pipelinerun and answer y",
command: []string{"rm", "tr0-1", "-n", "ns"},
Expand Down
8 changes: 8 additions & 0 deletions pkg/deleter/deleter.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ func (d *Deleter) PrintSuccesses(streams *cli.Stream) {
}
}

func (d *Deleter) SuccessfulDeletes() []string {
return append([]string(nil), d.successfulDeletes...)
}

func (d *Deleter) SuccessfulRelatedDeletes() []string {
return append([]string(nil), d.successfulRelatedDeletes...)
}

// appendError adds that error to the list of accumulated errors that
// have occurred during execution.
func (d *Deleter) appendError(err error) {
Expand Down
24 changes: 24 additions & 0 deletions pkg/deleter/deleter_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package deleter

import (
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -50,6 +51,29 @@ func TestDelete(t *testing.T) {
}
}

func TestSuccessfulDeletes(t *testing.T) {
d := New("FooBar", successfulDeleteFunc())
d.Delete([]string{"foo", "bar"})

expected := []string{"foo", "bar"}
if got := d.SuccessfulDeletes(); !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v, received %v", expected, got)
}
}

func TestSuccessfulRelatedDeletes(t *testing.T) {
d := New("FooBar", successfulDeleteFunc())
d.WithRelated("FooBarRun", successfulListFunc("fbr1", "fbr2"), successfulDeleteFunc())

deletedNames := d.Delete([]string{"foo"})
d.DeleteRelated(deletedNames)

expected := []string{"fbr1", "fbr2"}
if got := d.SuccessfulRelatedDeletes(); !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v, received %v", expected, got)
}
}

func TestDeleteRelated(t *testing.T) {
for _, tc := range []struct {
description string
Expand Down
Loading