Skip to content

Commit fa82032

Browse files
Debashichtekton-robot
authored andcommitted
feat: support structured output for taskrun delete
Signed-off-by: Debashich <debashishsinha555@gmail.com>
1 parent c23e958 commit fa82032

4 files changed

Lines changed: 193 additions & 56 deletions

File tree

‎docs/cmd/tkn_taskrun_delete.md‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,15 @@ or
2828
### Options
2929

3030
```
31-
--all Delete all TaskRuns in a namespace (default: false)
32-
--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)
33-
-f, --force Whether to force deletion (default: false)
34-
-h, --help help for delete
35-
-i, --ignore-running ignore running TaskRun (default true)
36-
--ignore-running-pipelinerun ignore deleting taskruns of a running PipelineRun (default true)
37-
--keep int Keep n most recent number of TaskRuns
38-
--keep-since int When deleting all TaskRuns keep the ones that has been completed since n minutes
39-
-o, --output string Output format. One of: (json, yaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).
40-
--show-managed-fields If true, keep the managedFields when printing objects in JSON or YAML format.
41-
-t, --task string The name of a Task whose TaskRuns should be deleted (does not delete the task)
42-
--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].
31+
--all Delete all TaskRuns in a namespace (default: false)
32+
-f, --force Whether to force deletion (default: false)
33+
-h, --help help for delete
34+
-i, --ignore-running ignore running TaskRun (default true)
35+
--ignore-running-pipelinerun ignore deleting taskruns of a running PipelineRun (default true)
36+
--keep int Keep n most recent number of TaskRuns
37+
--keep-since int When deleting all TaskRuns keep the ones that has been completed since n minutes
38+
-o, --output string Output format. One of: json|yaml. Skips the confirmation prompt
39+
-t, --task string The name of a Task whose TaskRuns should be deleted (does not delete the task)
4340
```
4441

4542
### Options inherited from parent commands

‎docs/man/man1/tkn-taskrun-delete.1‎

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ Delete TaskRuns in a namespace
2323
\fB\-\-all\fP[=false]
2424
Delete all TaskRuns in a namespace (default: false)
2525

26-
.PP
27-
\fB\-\-allow\-missing\-template\-keys\fP[=true]
28-
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.
29-
3026
.PP
3127
\fB\-f\fP, \fB\-\-force\fP[=false]
3228
Whether to force deletion (default: false)
@@ -53,21 +49,12 @@ Delete TaskRuns in a namespace
5349

5450
.PP
5551
\fB\-o\fP, \fB\-\-output\fP=""
56-
Output format. One of: (json, yaml, name, go\-template, go\-template\-file, template, templatefile, jsonpath, jsonpath\-as\-json, jsonpath\-file).
57-
58-
.PP
59-
\fB\-\-show\-managed\-fields\fP[=false]
60-
If true, keep the managedFields when printing objects in JSON or YAML format.
52+
Output format. One of: json|yaml. Skips the confirmation prompt
6153

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

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

7259
.SH OPTIONS INHERITED FROM PARENT COMMANDS
7360
.PP

‎pkg/cmd/taskrun/delete.go‎

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
3333
"go.uber.org/multierr"
3434
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35-
cliopts "k8s.io/cli-runtime/pkg/genericclioptions"
3635
)
3736

3837
type deleteOptions struct {
@@ -64,7 +63,6 @@ func trExists(args []string, p cli.Params) ([]string, error) {
6463
func deleteCommand(p cli.Params) *cobra.Command {
6564
opts := &options.DeleteOptions{Resource: "TaskRun", ForceDelete: false, DeleteAllNs: false}
6665
deleteOpts := &deleteOptions{}
67-
f := cliopts.NewPrintFlags("delete")
6866
eg := `Delete TaskRuns with names 'foo' and 'bar' in namespace 'quux':
6967
7068
tkn taskrun delete foo bar -n quux
@@ -92,6 +90,16 @@ or
9290
Err: cmd.OutOrStderr(),
9391
}
9492

93+
output, err := cmd.LocalFlags().GetString("output")
94+
if err != nil {
95+
return err
96+
}
97+
98+
output = formatted.NormalizeOutput(output)
99+
if output != "" && !formatted.IsStructured(output) {
100+
return fmt.Errorf("invalid output format %q: must be json or yaml", output)
101+
}
102+
95103
if deleteOpts.TaskName != "" {
96104
opts.ParentResource = "Task"
97105
opts.ParentResourceName = deleteOpts.TaskName
@@ -122,17 +130,22 @@ or
122130
return errs
123131
}
124132

125-
if err := opts.CheckOptions(s, availableTrs, p.Namespace()); err != nil {
133+
checkStreams := s
134+
if formatted.IsStructured(output) {
135+
s.Err = cmd.ErrOrStderr()
136+
checkStreams = &cli.Stream{In: strings.NewReader("y\n"), Out: &strings.Builder{}, Err: s.Err}
137+
}
138+
if err := opts.CheckOptions(checkStreams, availableTrs, p.Namespace()); err != nil {
126139
return err
127140
}
128141

129-
if err := deleteTaskRuns(s, p, availableTrs, opts); err != nil {
142+
if err := deleteTaskRuns(s, p, availableTrs, opts, output); err != nil {
130143
return err
131144
}
132145
return errs
133146
},
134147
}
135-
f.AddFlags(c)
148+
c.Flags().StringP("output", "o", "", formatted.DeleteOutputFlagUsage)
136149
c.Flags().BoolVarP(&opts.ForceDelete, "force", "f", false, "Whether to force deletion (default: false)")
137150
c.Flags().StringVarP(&deleteOpts.TaskName, "task", "t", "", "The name of a Task whose TaskRuns should be deleted (does not delete the task)")
138151
c.Flags().BoolVarP(&opts.DeleteAllNs, "all", "", false, "Delete all TaskRuns in a namespace (default: false)")
@@ -144,7 +157,7 @@ or
144157
return c
145158
}
146159

147-
func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options.DeleteOptions) error {
160+
func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options.DeleteOptions, output string) error {
148161
var numberOfDeletedTr, numberOfKeptTr int
149162
cs, err := p.Clients()
150163
if err != nil {
@@ -177,9 +190,17 @@ func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options
177190
prFinished := ownerPrFinished(cs, *tr)
178191

179192
if !prFinished && opts.ForceDelete {
180-
fmt.Fprintf(s.Out, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
193+
if formatted.IsStructured(output) {
194+
fmt.Fprintf(s.Err, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
195+
} else {
196+
fmt.Fprintf(s.Out, "warning: Taskrun %s related pipelinerun still running.\n", tr.Name)
197+
}
181198
}
182199
if !prFinished && !opts.ForceDelete {
200+
if formatted.IsStructured(output) {
201+
return fmt.Errorf("taskrun %s is owned by a running PipelineRun; use --force to delete", tr.Name)
202+
}
203+
183204
fmt.Fprintf(s.Out, "TaskRun(s): %s attached to PipelineRun is still running deleting will restart the completed taskrun. Proceed (y/n): ", tr.Name)
184205
if err := opts.TakeInput(s, ""); err != nil {
185206
continue
@@ -211,15 +232,26 @@ func deleteTaskRuns(s *cli.Stream, p cli.Params, trNames []string, opts *options
211232
return actions.Delete(taskrunGroupResource, cs.Dynamic, cs.Tekton.Discovery(), taskRunName, p.Namespace(), metav1.DeleteOptions{})
212233
})
213234

214-
if opts.Keep > 0 && opts.Keep == len(trToKeep) && len(trToDelete) == 0 {
215-
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)
216-
return nil
217-
}
218-
if opts.Keep > len(trToKeep) {
219-
fmt.Fprintf(s.Out, "There is/are only %d %s(s) associated for %s: %s \n", len(trToKeep), opts.Resource, opts.ParentResource, opts.ParentResourceName)
220-
return nil
235+
switch {
236+
case opts.Keep > 0 && opts.Keep == len(trToKeep) && len(trToDelete) == 0:
237+
if !formatted.IsStructured(output) {
238+
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)
239+
return nil
240+
}
241+
case opts.Keep > len(trToKeep):
242+
if !formatted.IsStructured(output) {
243+
fmt.Fprintf(s.Out, "There is/are only %d %s(s) associated for %s: %s \n", len(trToKeep), opts.Resource, opts.ParentResource, opts.ParentResourceName)
244+
return nil
245+
}
246+
default:
247+
d.DeleteRelated([]string{opts.ParentResourceName})
221248
}
222-
d.DeleteRelated([]string{opts.ParentResourceName})
249+
}
250+
251+
if formatted.IsStructured(output) {
252+
deleted := append(d.SuccessfulRelatedDeletes(), d.SuccessfulDeletes()...)
253+
printErr := formatted.PrintStructuredOutput(s.Out, output, formatted.NewDeleteResult(deleted))
254+
return multierr.Append(printErr, d.Errors())
223255
}
224256

225257
if !opts.DeleteAllNs {

‎pkg/cmd/taskrun/delete_test.go‎

Lines changed: 136 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package taskrun
1616

1717
import (
18+
"bytes"
19+
"encoding/json"
1820
"io"
1921
"strings"
2022
"testing"
@@ -228,7 +230,7 @@ func TestTaskRunDelete_v1beta1(t *testing.T) {
228230
}
229231

230232
seeds := make([]clients, 0)
231-
for i := 0; i < 17; i++ {
233+
for i := 0; i < 18; i++ {
232234
trs := trdata
233235
cs, _ := test.SeedV1beta1TestData(t, test.Data{TaskRuns: trs, Tasks: tasks, Namespaces: ns})
234236
cs.Pipeline.Resources = cb.APIResourceList(versionv1beta1, []string{"taskrun"})
@@ -572,6 +574,82 @@ func TestTaskRunDelete_v1beta1(t *testing.T) {
572574
wantError: false,
573575
want: "All 6 TaskRuns associated with Task \"random\" deleted in namespace \"ns\"\n",
574576
},
577+
{
578+
name: "With JSON output",
579+
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "json"},
580+
dynamic: seeds[14].dynamicClient,
581+
input: seeds[14].pipelineClient,
582+
inputStream: nil,
583+
wantError: false,
584+
want: `{
585+
"deleted": [
586+
"tr0-1"
587+
]
588+
}
589+
`,
590+
},
591+
{
592+
name: "With YAML output",
593+
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "yaml"},
594+
dynamic: seeds[17].dynamicClient,
595+
input: seeds[17].pipelineClient,
596+
inputStream: nil,
597+
wantError: false,
598+
want: "deleted:\n- tr0-1\n",
599+
},
600+
{
601+
name: "Unsupported output format",
602+
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "table"},
603+
dynamic: seeds[0].dynamicClient,
604+
input: seeds[0].pipelineClient,
605+
inputStream: nil,
606+
wantError: true,
607+
want: "invalid output format \"table\": must be json or yaml",
608+
},
609+
{
610+
name: "With JSON output for multiple TaskRuns",
611+
command: []string{"rm", "tr0-1", "tr0-2", "-n", "ns", "-o", "json"},
612+
dynamic: seeds[16].dynamicClient,
613+
input: seeds[16].pipelineClient,
614+
inputStream: strings.NewReader("y\n"),
615+
wantError: false,
616+
want: `{
617+
"deleted": [
618+
"tr0-1",
619+
"tr0-2"
620+
]
621+
}
622+
`,
623+
},
624+
{
625+
name: "Delete with JSON output for --task but keep meets or exceeds existing (no-op)",
626+
command: []string{"rm", "--task", "random", "-n", "ns", "--keep", "10", "-o", "json"},
627+
dynamic: seeds[15].dynamicClient,
628+
input: seeds[15].pipelineClient,
629+
inputStream: nil,
630+
wantError: false,
631+
want: `{
632+
"deleted": []
633+
}
634+
`,
635+
},
636+
{
637+
name: "Delete with JSON output for --task",
638+
command: []string{"rm", "--task", "random", "-n", "ns", "-o", "json"},
639+
dynamic: seeds[15].dynamicClient,
640+
input: seeds[15].pipelineClient,
641+
inputStream: nil,
642+
wantError: false,
643+
want: `{
644+
"deleted": [
645+
"tr0-1",
646+
"tr0-2",
647+
"tr0-3",
648+
"tr0-9"
649+
]
650+
}
651+
`,
652+
},
575653
}
576654

577655
for _, tp := range testParams {
@@ -1318,14 +1396,24 @@ func Test_TaskRuns_Delete_With_Running_PipelineRun_v1beta1(t *testing.T) {
13181396
seeds = append(seeds, clients{cs, dc})
13191397
}
13201398
testParams := []struct {
1321-
name string
1322-
command []string
1323-
dynamic dynamic.Interface
1324-
input test.Clients
1325-
inputStream io.Reader
1326-
wantError bool
1327-
want string
1399+
name string
1400+
command []string
1401+
dynamic dynamic.Interface
1402+
input test.Clients
1403+
inputStream io.Reader
1404+
wantError bool
1405+
want string
1406+
assertStreams bool
13281407
}{
1408+
{
1409+
name: "Taskrun with running pipelinerun JSON without force",
1410+
command: []string{"rm", "tr0-1", "-n", "ns", "-o", "json"},
1411+
dynamic: seeds[0].dynamicClient,
1412+
input: seeds[0].pipelineClient,
1413+
inputStream: nil,
1414+
wantError: true,
1415+
want: "taskrun tr0-1 is owned by a running PipelineRun; use --force to delete",
1416+
},
13291417
{
13301418
name: "Taskrun with running pipelinerun and answer y",
13311419
command: []string{"rm", "tr0-1", "-n", "ns"},
@@ -1542,13 +1630,14 @@ func Test_TaskRuns_Delete_With_Running_PipelineRun(t *testing.T) {
15421630
seeds = append(seeds, clients{cs, dc})
15431631
}
15441632
testParams := []struct {
1545-
name string
1546-
command []string
1547-
dynamic dynamic.Interface
1548-
input pipelinetest.Clients
1549-
inputStream io.Reader
1550-
wantError bool
1551-
want string
1633+
name string
1634+
command []string
1635+
dynamic dynamic.Interface
1636+
input pipelinetest.Clients
1637+
inputStream io.Reader
1638+
wantError bool
1639+
want string
1640+
assertStreams bool
15521641
}{
15531642
{
15541643
name: "Taskrun with running pipelinerun and answer y",
@@ -1568,6 +1657,14 @@ func Test_TaskRuns_Delete_With_Running_PipelineRun(t *testing.T) {
15681657
wantError: false,
15691658
want: "warning: Taskrun tr0-1 related pipelinerun still running.\nTaskRuns deleted: \"tr0-1\"\n",
15701659
},
1660+
{
1661+
name: "Taskrun with running pipelinerun force JSON streams",
1662+
command: []string{"rm", "tr0-1", "-n", "ns", "--force", "-o", "json"},
1663+
dynamic: seeds[1].dynamicClient,
1664+
input: seeds[1].pipelineClient,
1665+
wantError: false,
1666+
assertStreams: true,
1667+
},
15711668
{
15721669
name: "All taskruns with running pipelinerun ",
15731670
command: []string{"delete", "-n", "ns", "--all"},
@@ -1595,6 +1692,30 @@ func Test_TaskRuns_Delete_With_Running_PipelineRun(t *testing.T) {
15951692
if tp.inputStream != nil {
15961693
taskrun.SetIn(tp.inputStream)
15971694
}
1695+
if tp.assertStreams {
1696+
var stdout, stderr bytes.Buffer
1697+
taskrun.SetOut(&stdout)
1698+
taskrun.SetErr(&stderr)
1699+
taskrun.SetArgs(tp.command)
1700+
1701+
if err := taskrun.Execute(); err != nil {
1702+
t.Fatalf("unexpected error: %v", err)
1703+
}
1704+
1705+
if !json.Valid(stdout.Bytes()) {
1706+
t.Errorf("stdout is not valid JSON: %q", stdout.String())
1707+
}
1708+
1709+
if strings.Contains(stdout.String(), "warning:") {
1710+
t.Errorf("warning leaked into stdout: %q", stdout.String())
1711+
}
1712+
1713+
if !strings.Contains(stderr.String(), "warning:") {
1714+
t.Errorf("expected warning on stderr, got: %q", stderr.String())
1715+
}
1716+
1717+
return
1718+
}
15981719

15991720
out, err := test.ExecuteCommand(taskrun, tp.command...)
16001721
if tp.wantError {

0 commit comments

Comments
 (0)