diff --git a/docs/cmd/tkn_pipelinerun_logs.md b/docs/cmd/tkn_pipelinerun_logs.md index 80aa30040..aa0bb0d99 100644 --- a/docs/cmd/tkn_pipelinerun_logs.md +++ b/docs/cmd/tkn_pipelinerun_logs.md @@ -30,11 +30,11 @@ Show the logs of PipelineRun named 'microservice-1' for all tasks and steps (inc ### Options ``` - -a, --all show all logs including init steps injected by tekton - -f, --follow stream live logs - -h, --help help for logs - --limit int lists number of pipelineruns (default 5) - -t, --only-tasks strings show logs for mentioned tasks only + -a, --all show all logs including init steps injected by tekton + -f, --follow stream live logs + -h, --help help for logs + --limit int lists number of pipelineruns (default 5) + -t, --task strings show logs for mentioned tasks only ``` ### Options inherited from parent commands diff --git a/docs/cmd/tkn_taskrun_logs.md b/docs/cmd/tkn_taskrun_logs.md index bfd4ddabb..5a10219ed 100644 --- a/docs/cmd/tkn_taskrun_logs.md +++ b/docs/cmd/tkn_taskrun_logs.md @@ -22,14 +22,19 @@ Show the live logs of TaskRun named 'foo' from namespace 'bar': tkn taskrun logs -f foo -n bar +Show the logs of TaskRun named 'microservice-1' for step 'build' only from namespace 'bar': + + tkn tr logs microservice-1 -s build -n bar + ### Options ``` - -a, --all show all logs including init steps injected by tekton - -f, --follow stream live logs - -h, --help help for logs - --limit int lists number of taskruns (default 5) + -a, --all show all logs including init steps injected by tekton + -f, --follow stream live logs + -h, --help help for logs + --limit int lists number of taskruns (default 5) + -s, --step strings show logs for mentioned steps only ``` ### Options inherited from parent commands diff --git a/docs/man/man1/tkn-pipelinerun-logs.1 b/docs/man/man1/tkn-pipelinerun-logs.1 index 47763b12a..00ecc50d4 100644 --- a/docs/man/man1/tkn-pipelinerun-logs.1 +++ b/docs/man/man1/tkn-pipelinerun-logs.1 @@ -36,7 +36,7 @@ Show the logs of PipelineRun lists number of pipelineruns .PP -\fB\-t\fP, \fB\-\-only\-tasks\fP=[] +\fB\-t\fP, \fB\-\-task\fP=[] show logs for mentioned tasks only diff --git a/docs/man/man1/tkn-taskrun-logs.1 b/docs/man/man1/tkn-taskrun-logs.1 index 5ab7fe387..5ba12a252 100644 --- a/docs/man/man1/tkn-taskrun-logs.1 +++ b/docs/man/man1/tkn-taskrun-logs.1 @@ -35,6 +35,10 @@ Show taskruns logs \fB\-\-limit\fP=5 lists number of taskruns +.PP +\fB\-s\fP, \fB\-\-step\fP=[] + show logs for mentioned steps only + .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP @@ -79,6 +83,18 @@ tkn taskrun logs \-f foo \-n bar .fi .RE +.PP +Show the logs of TaskRun named 'microservice\-1' for step 'build' only from namespace 'bar': + +.PP +.RS + +.nf +tkn tr logs microservice\-1 \-s build \-n bar + +.fi +.RE + .SH SEE ALSO .PP diff --git a/pkg/cmd/pipelinerun/log_test.go b/pkg/cmd/pipelinerun/log_test.go index 85d2157bb..b9172b026 100644 --- a/pkg/cmd/pipelinerun/log_test.go +++ b/pkg/cmd/pipelinerun/log_test.go @@ -880,7 +880,7 @@ func updatePR(finalRuns []*v1alpha1.PipelineRun, watcher *watch.FakeWatcher) { }() } -func logOpts(name string, ns string, cs pipelinetest.Clients, streamer stream.NewStreamerFunc, allSteps bool, follow bool, onlyTasks ...string) *options.LogOptions { +func logOpts(name string, ns string, cs pipelinetest.Clients, streamer stream.NewStreamerFunc, allSteps bool, follow bool, tasks ...string) *options.LogOptions { p := test.Params{ Kube: cs.Kube, Tekton: cs.Pipeline, @@ -889,7 +889,7 @@ func logOpts(name string, ns string, cs pipelinetest.Clients, streamer stream.Ne logOptions := options.LogOptions{ PipelineRunName: name, - Tasks: onlyTasks, + Tasks: tasks, AllSteps: allSteps, Follow: follow, Params: &p, diff --git a/pkg/cmd/pipelinerun/logs.go b/pkg/cmd/pipelinerun/logs.go index 426b6a838..377b32b5b 100644 --- a/pkg/cmd/pipelinerun/logs.go +++ b/pkg/cmd/pipelinerun/logs.go @@ -70,7 +70,7 @@ Show the logs of PipelineRun named 'microservice-1' for all tasks and steps (inc c.Flags().BoolVarP(&opts.AllSteps, "all", "a", false, "show all logs including init steps injected by tekton") c.Flags().BoolVarP(&opts.Follow, "follow", "f", false, "stream live logs") - c.Flags().StringSliceVarP(&opts.Tasks, "only-tasks", "t", []string{}, "show logs for mentioned tasks only") + c.Flags().StringSliceVarP(&opts.Tasks, "task", "t", []string{}, "show logs for mentioned tasks only") c.Flags().IntVarP(&opts.Limit, "limit", "", 5, "lists number of pipelineruns") _ = c.MarkZshCompPositionalArgumentCustom(1, "__tkn_get_pipelinerun") diff --git a/pkg/cmd/taskrun/log_reader.go b/pkg/cmd/taskrun/log_reader.go index 6a65ac700..9bfd6fd25 100644 --- a/pkg/cmd/taskrun/log_reader.go +++ b/pkg/cmd/taskrun/log_reader.go @@ -57,6 +57,7 @@ type LogReader struct { Follow bool AllSteps bool Stream *cli.Stream + Steps []string } func (lr *LogReader) Read() (<-chan Log, <-chan error, error) { @@ -113,7 +114,7 @@ func (lr *LogReader) readLiveLogs() (<-chan Log, <-chan error, error) { return nil, nil, errors.New(fmt.Sprintf("task %s failed: %s. Run tkn tr desc %s for more details.", lr.Task, strings.TrimSpace(err.Error()), tr.Name)) } - steps := filterSteps(pod, lr.AllSteps) + steps := filterSteps(pod, lr.AllSteps, lr.Steps) logC, errC := lr.readStepsLogs(steps, p, lr.Follow) return logC, errC, err } @@ -143,7 +144,7 @@ func (lr *LogReader) readAvailableLogs(tr *v1alpha1.TaskRun) (<-chan Log, <-chan return nil, nil, errors.New(fmt.Sprintf("task %s failed: %s. Run tkn tr desc %s for more details.", lr.Task, strings.TrimSpace(err.Error()), tr.Name)) } - steps := filterSteps(pod, lr.AllSteps) + steps := filterSteps(pod, lr.AllSteps, lr.Steps) logC, errC := lr.readStepsLogs(steps, p, lr.Follow) return logC, errC, nil } @@ -198,14 +199,29 @@ func (lr *LogReader) readStepsLogs(steps []*step, pod *pods.Pod, follow bool) (< return logC, errC } -func filterSteps(pod *corev1.Pod, allSteps bool) []*step { +func filterSteps(pod *corev1.Pod, allSteps bool, stepsGiven []string) []*step { steps := []*step{} + stepsInPod := getSteps(pod) if allSteps { steps = append(steps, getInitSteps(pod)...) } - steps = append(steps, getSteps(pod)...) + if len(stepsGiven) == 0 { + steps = append(steps, stepsInPod...) + return steps + } + + stepsToAdd := map[string]bool{} + for _, s := range stepsGiven { + stepsToAdd[s] = true + } + + for _, sp := range stepsInPod { + if stepsToAdd[sp.name] { + steps = append(steps, sp) + } + } return steps } diff --git a/pkg/cmd/taskrun/logs.go b/pkg/cmd/taskrun/logs.go index cdb812b83..a4d03637f 100644 --- a/pkg/cmd/taskrun/logs.go +++ b/pkg/cmd/taskrun/logs.go @@ -40,6 +40,10 @@ func logCommand(p cli.Params) *cobra.Command { Show the live logs of TaskRun named 'foo' from namespace 'bar': tkn taskrun logs -f foo -n bar + +Show the logs of TaskRun named 'microservice-1' for step 'build' only from namespace 'bar': + + tkn tr logs microservice-1 -s build -n bar ` c := &cobra.Command{ Use: "logs", @@ -63,6 +67,10 @@ Show the live logs of TaskRun named 'foo' from namespace 'bar': return err } + if len(opts.Steps) > 0 && opts.AllSteps { + return fmt.Errorf("option --all and option --step are not compatible") + } + return Run(opts) }, } @@ -70,6 +78,7 @@ Show the live logs of TaskRun named 'foo' from namespace 'bar': c.Flags().BoolVarP(&opts.AllSteps, "all", "a", false, "show all logs including init steps injected by tekton") c.Flags().BoolVarP(&opts.Follow, "follow", "f", false, "stream live logs") c.Flags().IntVarP(&opts.Limit, "limit", "", 5, "lists number of taskruns") + c.Flags().StringSliceVarP(&opts.Steps, "step", "s", []string{}, "show logs for mentioned steps only") _ = c.MarkZshCompPositionalArgumentCustom(1, "__tkn_get_taskrun") return c @@ -100,6 +109,7 @@ func Run(opts *options.LogOptions) error { Stream: opts.Stream, Follow: opts.Follow, AllSteps: opts.AllSteps, + Steps: opts.Steps, } logC, errC, err := lr.Read() diff --git a/pkg/cmd/taskrun/logs_test.go b/pkg/cmd/taskrun/logs_test.go index fddd582bf..5e2ceed75 100644 --- a/pkg/cmd/taskrun/logs_test.go +++ b/pkg/cmd/taskrun/logs_test.go @@ -134,7 +134,7 @@ func TestLog_no_taskrun_arg(t *testing.T) { for _, tp := range testParams { t.Run(tp.name, func(t *testing.T) { - trlo := logOpts("", "ns", tp.input, fake.Streamer(fake.Logs()), false, false) + trlo := logOpts("", "ns", tp.input, fake.Streamer(fake.Logs()), false, false, []string{}) _, err := fetchLogs(trlo) if tp.wantError { if err == nil { @@ -173,6 +173,24 @@ func TestLog_missing_taskrun(t *testing.T) { test.AssertOutput(t, expected, got) } +func TestLog_invalid_flags(t *testing.T) { + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + }, + }, + } + + cs, _ := test.SeedTestData(t, pipelinetest.Data{Namespaces: nsList}) + p := &test.Params{Tekton: cs.Pipeline, Kube: cs.Kube} + + c := Command(p) + got, _ := test.ExecuteCommand(c, "logs", "output-taskrun-2", "-n", "ns", "-a", "--step", "test") + expected := "Error: option --all and option --step are not compatible\n" + test.AssertOutput(t, expected, got) +} + func TestLog_taskrun_logs(t *testing.T) { var ( ns = "namespace" @@ -236,7 +254,7 @@ func TestLog_taskrun_logs(t *testing.T) { ) cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: ps, Namespaces: nsList}) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, false) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, false, []string{}) output, _ := fetchLogs(trlo) expectedLogs := []string{ @@ -294,7 +312,7 @@ func TestLog_taskrun_logs_no_pod_name(t *testing.T) { logs := fake.Logs() cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: ps, Namespaces: nsList}) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, false) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, false, []string{}) _, err := fetchLogs(trlo) if err == nil { @@ -379,7 +397,7 @@ func TestLog_taskrun_all_steps(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) - trl := logOpts(trName, ns, cs, fake.Streamer(logs), true, false) + trl := logOpts(trName, ns, cs, fake.Streamer(logs), true, false, []string{}) output, _ := fetchLogs(trl) expectedLogs := []string{ @@ -393,6 +411,90 @@ func TestLog_taskrun_all_steps(t *testing.T) { test.AssertOutput(t, expected, output) } +func TestLog_taskrun_given_steps(t *testing.T) { + var ( + ns = "namespace" + taskName = "output-task" + + trName = "output-task-run" + trStartTime = clockwork.NewFakeClock().Now().Add(20 * time.Second) + trPod = "output-task-pod-123456" + trInitStep1 = "credential-initializer-mdzbr" + trInitStep2 = "place-tools" + trStep1Name = "writefile-step" + nopStep = "nop" + ) + + trs := []*v1alpha1.TaskRun{ + tb.TaskRun(trName, ns, + tb.TaskRunStatus( + tb.PodName(trPod), + tb.TaskRunStartTime(trStartTime), + tb.StatusCondition(apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }), + tb.StepState( + cb.StepName(trStep1Name), + tb.StateTerminated(0), + ), + tb.StepState( + cb.StepName(nopStep), + tb.StateTerminated(0), + ), + ), + tb.TaskRunSpec( + tb.TaskRunTaskRef(taskName), + ), + ), + } + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace", + }, + }, + } + + p := []*corev1.Pod{ + tb.Pod(trPod, ns, + tb.PodSpec( + tb.PodInitContainer(trInitStep1, "override-with-creds:latest"), + tb.PodInitContainer(trInitStep2, "override-with-tools:latest"), + tb.PodContainer(trStep1Name, trStep1Name+":latest"), + tb.PodContainer(nopStep, "override-with-nop:latest"), + ), + cb.PodStatus( + cb.PodPhase(corev1.PodSucceeded), + cb.PodInitContainerStatus(trInitStep1, "override-with-creds:latest"), + cb.PodInitContainerStatus(trInitStep2, "override-with-tools:latest"), + ), + ), + } + + logs := fake.Logs( + fake.Task(trPod, + fake.Step(trInitStep1, "initialized the credentials"), + fake.Step(trInitStep2, "place tools log"), + fake.Step(trStep1Name, "written a file"), + fake.Step(nopStep, "Build successful"), + ), + ) + + cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) + + trl := logOpts(trName, ns, cs, fake.Streamer(logs), false, false, []string{trStep1Name}) + output, _ := fetchLogs(trl) + + expectedLogs := []string{ + "[writefile-step] written a file\n", + } + expected := strings.Join(expectedLogs, "\n") + "\n" + + test.AssertOutput(t, expected, output) +} + func TestLog_taskrun_follow_mode(t *testing.T) { var ( prstart = clockwork.NewFakeClock() @@ -466,7 +568,7 @@ func TestLog_taskrun_follow_mode(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true, []string{}) output, _ := fetchLogs(trlo) expectedLogs := []string{ @@ -550,7 +652,7 @@ func TestLog_taskrun_follow_mode_no_pod_name(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true, []string{}) _, err := fetchLogs(trlo) if err == nil { @@ -634,7 +736,7 @@ func TestLog_taskrun_follow_mode_update_pod_name(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) watcher := watch.NewRaceFreeFake() cs.Pipeline.PrependWatchReactor("taskruns", k8stest.DefaultWatchReactor(watcher, nil)) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true, []string{}) go func() { time.Sleep(time.Second * 1) @@ -728,7 +830,7 @@ func TestLog_taskrun_follow_mode_update_timeout(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) watcher := watch.NewRaceFreeFake() cs.Pipeline.PrependWatchReactor("taskruns", k8stest.DefaultWatchReactor(watcher, nil)) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true, []string{}) go func() { time.Sleep(time.Second * 1) @@ -816,7 +918,7 @@ func TestLog_taskrun_follow_mode_no_output_provided(t *testing.T) { cs, _ := test.SeedTestData(t, pipelinetest.Data{TaskRuns: trs, Pods: p, Namespaces: nsList}) watcher := watch.NewRaceFreeFake() cs.Pipeline.PrependWatchReactor("taskruns", k8stest.DefaultWatchReactor(watcher, nil)) - trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true) + trlo := logOpts(trName, ns, cs, fake.Streamer(logs), false, true, []string{}) _, err := fetchLogs(trlo) if err == nil { @@ -827,7 +929,7 @@ func TestLog_taskrun_follow_mode_no_output_provided(t *testing.T) { test.AssertOutput(t, expected, err.Error()) } -func logOpts(run, ns string, cs pipelinetest.Clients, streamer stream.NewStreamerFunc, allSteps bool, follow bool) *options.LogOptions { +func logOpts(run, ns string, cs pipelinetest.Clients, streamer stream.NewStreamerFunc, allSteps bool, follow bool, steps []string) *options.LogOptions { p := test.Params{ Kube: cs.Kube, Tekton: cs.Pipeline, @@ -841,6 +943,7 @@ func logOpts(run, ns string, cs pipelinetest.Clients, streamer stream.NewStreame Params: &p, Streamer: streamer, Limit: 5, + Steps: steps, } } diff --git a/pkg/helper/options/logs.go b/pkg/helper/options/logs.go index 75c7c05f2..0346ce607 100644 --- a/pkg/helper/options/logs.go +++ b/pkg/helper/options/logs.go @@ -43,6 +43,7 @@ type LogOptions struct { Stream *cli.Stream Streamer stream.NewStreamerFunc Tasks []string + Steps []string Last bool Limit int AskOpts survey.AskOpt