From dffdcad679605d0f01da87d54ebe6936c355bf58 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Fri, 26 Apr 2024 14:09:16 +0200 Subject: [PATCH] Add -E flag to exit with the pr sate on unix shell Add the -E or --exit-with-pipelinerun-error flag to tkn pr logs to exit with the pipelinerun error after showing the logs. Add the option as well to tkn pipeline start to pass down to the logs command. Signed-off-by: Chmouel Boudjnah --- docs/cmd/tkn_pipeline_start.md | 1 + docs/cmd/tkn_pipelinerun_logs.md | 19 ++++---- docs/man/man1/tkn-pipeline-start.1 | 4 ++ docs/man/man1/tkn-pipelinerun-logs.1 | 4 ++ pkg/cmd/pipeline/start.go | 3 ++ pkg/cmd/pipelinerun/logs.go | 26 +++++++++++ pkg/cmd/pipelinerun/logs_test.go | 65 +++++++++++++++++++++++++--- pkg/options/logs.go | 1 + 8 files changed, 108 insertions(+), 15 deletions(-) diff --git a/docs/cmd/tkn_pipeline_start.md b/docs/cmd/tkn_pipeline_start.md index dfdf64c21..7c3acf173 100644 --- a/docs/cmd/tkn_pipeline_start.md +++ b/docs/cmd/tkn_pipeline_start.md @@ -63,6 +63,7 @@ my-csi-template and my-volume-claim-template) ``` --dry-run preview PipelineRun without running it + -E, --exit-with-pipelinerun-error when using --showlog, exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status -f, --filename string local or remote file name containing a Pipeline definition to start a PipelineRun --finally-timeout string timeout for Finally TaskRuns -h, --help help for start diff --git a/docs/cmd/tkn_pipelinerun_logs.md b/docs/cmd/tkn_pipelinerun_logs.md index 7163cae38..1648dc4d0 100644 --- a/docs/cmd/tkn_pipelinerun_logs.md +++ b/docs/cmd/tkn_pipelinerun_logs.md @@ -30,15 +30,16 @@ 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 - -F, --fzf use fzf to select a PipelineRun - -h, --help help for logs - -L, --last show logs for last PipelineRun - --limit int lists number of PipelineRuns (default 5) - --prefix prefix each log line with the log source (task name and step name) (default true) - -t, --task strings show logs for mentioned Tasks only - --timestamps show logs with timestamp + -a, --all show all logs including init steps injected by tekton + -E, --exit-with-pipelinerun-error exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status + -f, --follow stream live logs + -F, --fzf use fzf to select a PipelineRun + -h, --help help for logs + -L, --last show logs for last PipelineRun + --limit int lists number of PipelineRuns (default 5) + --prefix prefix each log line with the log source (task name and step name) (default true) + -t, --task strings show logs for mentioned Tasks only + --timestamps show logs with timestamp ``` ### Options inherited from parent commands diff --git a/docs/man/man1/tkn-pipeline-start.1 b/docs/man/man1/tkn-pipeline-start.1 index 5ef8ab22f..eac1634b6 100644 --- a/docs/man/man1/tkn-pipeline-start.1 +++ b/docs/man/man1/tkn-pipeline-start.1 @@ -31,6 +31,10 @@ Parameters, at least those that have no default value \fB\-\-dry\-run\fP[=false] preview PipelineRun without running it +.PP +\fB\-E\fP, \fB\-\-exit\-with\-pipelinerun\-error\fP[=false] + when using \-\-showlog, exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status + .PP \fB\-f\fP, \fB\-\-filename\fP="" local or remote file name containing a Pipeline definition to start a PipelineRun diff --git a/docs/man/man1/tkn-pipelinerun-logs.1 b/docs/man/man1/tkn-pipelinerun-logs.1 index 953eb1a7e..a5879ea6d 100644 --- a/docs/man/man1/tkn-pipelinerun-logs.1 +++ b/docs/man/man1/tkn-pipelinerun-logs.1 @@ -23,6 +23,10 @@ Show the logs of a PipelineRun \fB\-a\fP, \fB\-\-all\fP[=false] show all logs including init steps injected by tekton +.PP +\fB\-E\fP, \fB\-\-exit\-with\-pipelinerun\-error\fP[=false] + exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status + .PP \fB\-f\fP, \fB\-\-follow\fP[=false] stream live logs diff --git a/pkg/cmd/pipeline/start.go b/pkg/cmd/pipeline/start.go index 7c7a7797c..53a49326f 100644 --- a/pkg/cmd/pipeline/start.go +++ b/pkg/cmd/pipeline/start.go @@ -68,6 +68,7 @@ type startOptions struct { Labels []string ShowLog bool DryRun bool + ExitWithPrError bool Output string PrefixName string TimeOut string @@ -195,6 +196,7 @@ For passing the workspaces via flags: c.Flags().BoolVarP(&opt.UseParamDefaults, "use-param-defaults", "", false, "use default parameter values without prompting for input") c.Flags().StringVar(&opt.PodTemplate, "pod-template", "", "local or remote file containing a PodTemplate definition") c.Flags().BoolVarP(&opt.SkipOptionalWorkspace, "skip-optional-workspace", "", false, "skips the prompt for optional workspaces") + c.Flags().BoolVarP(&opt.ExitWithPrError, "exit-with-pipelinerun-error", "E", false, "when using --showlog, exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status") c.Flags().StringVarP(&opt.ServiceAccountName, "serviceaccount", "s", "", "pass the serviceaccount name") _ = c.RegisterFlagCompletionFunc("serviceaccount", @@ -413,6 +415,7 @@ func (opt *startOptions) startPipeline(pipelineStart *v1beta1.Pipeline) error { Prefixing: true, Params: opt.cliparams, AllSteps: false, + ExitWithPrError: opt.ExitWithPrError, } return prcmd.Run(runLogOpts) } diff --git a/pkg/cmd/pipelinerun/logs.go b/pkg/cmd/pipelinerun/logs.go index fd2feb868..235003cfc 100644 --- a/pkg/cmd/pipelinerun/logs.go +++ b/pkg/cmd/pipelinerun/logs.go @@ -25,6 +25,8 @@ import ( "github.com/tektoncd/cli/pkg/log" "github.com/tektoncd/cli/pkg/options" pipelinerunpkg "github.com/tektoncd/cli/pkg/pipelinerun" + tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -82,6 +84,7 @@ Show the logs of PipelineRun named 'microservice-1' for all Tasks and steps (inc c.Flags().BoolVarP(&opts.Follow, "follow", "f", false, "stream live logs") c.Flags().BoolVarP(&opts.Timestamps, "timestamps", "", false, "show logs with timestamp") c.Flags().BoolVarP(&opts.Prefixing, "prefix", "", true, "prefix each log line with the log source (task name and step name)") + c.Flags().BoolVarP(&opts.ExitWithPrError, "exit-with-pipelinerun-error", "E", false, "exit with pipelinerun to the unix shell, 0 if success, 1 if error, 2 on unknown status") c.Flags().StringSliceVarP(&opts.Tasks, "task", "t", []string{}, "show logs for mentioned Tasks only") c.Flags().IntVarP(&opts.Limit, "limit", "", defaultLimit, "lists number of PipelineRuns") return c @@ -109,9 +112,32 @@ func Run(opts *options.LogOptions) error { log.NewWriter(log.LogTypePipeline, opts.Prefixing).Write(opts.Stream, logC, errC) + // get pipelinerun status + if opts.ExitWithPrError { + clients, err := opts.Params.Clients() + if err != nil { + return err + } + pr, err := pipelinerunpkg.GetPipelineRun(pipelineRunGroupResource, clients, opts.PipelineRunName, opts.Params.Namespace()) + if err != nil { + return err + } + os.Exit(prStatusToUnixStatus(pr)) + } + return nil } +func prStatusToUnixStatus(pr *tektonv1.PipelineRun) int { + if len(pr.Status.Conditions) == 0 { + return 2 + } + if pr.Status.Conditions[0].Status == corev1.ConditionFalse { + return 1 + } + return 0 +} + func askRunName(opts *options.LogOptions) error { lOpts := metav1.ListOptions{} diff --git a/pkg/cmd/pipelinerun/logs_test.go b/pkg/cmd/pipelinerun/logs_test.go index e9d2430b5..1528fb3f7 100644 --- a/pkg/cmd/pipelinerun/logs_test.go +++ b/pkg/cmd/pipelinerun/logs_test.go @@ -182,6 +182,65 @@ func TestLog_no_pipelinerun_argument(t *testing.T) { } } +func TestLog_PrStatusToUnixStatus(t *testing.T) { + testCases := []struct { + name string + pr *v1.PipelineRun + expected int + }{ + { + name: "No conditions", + pr: &v1.PipelineRun{ + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + expected: 2, + }, + { + name: "Condition status is false", + pr: &v1.PipelineRun{ + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionFalse, + }, + }, + }, + }, + }, + expected: 1, + }, + { + name: "Condition status true", + pr: &v1.PipelineRun{ + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + }, + }, + }, + }, + }, + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := prStatusToUnixStatus(tc.pr) + if result != tc.expected { + t.Errorf("Expected %d, got %d", tc.expected, result) + } + }) + } +} + func TestLog_run_found_v1beta1(t *testing.T) { clock := test.FakeClock() pdata := []*v1beta1.Pipeline{ @@ -1673,7 +1732,6 @@ func TestPipelinerunLog_follow_mode_v1beta1(t *testing.T) { cb.UnstructuredV1beta1PR(prs[0], versionv1beta1), cb.UnstructuredV1beta1P(pps[0], versionv1beta1), ) - if err != nil { t.Errorf("unable to create dynamic client: %v", err) } @@ -1862,7 +1920,6 @@ func TestPipelinerunLog_follow_mode(t *testing.T) { cb.UnstructuredPR(prs[0], version), cb.UnstructuredP(pps[0], version), ) - if err != nil { t.Errorf("unable to create dynamic client: %v", err) } @@ -2517,7 +2574,6 @@ func TestLog_pipelinerun_still_running_v1beta1(t *testing.T) { updatePRv1beta1(finalPRs, watcher) output, err := fetchLogs(prlo) - if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -2648,7 +2704,6 @@ func TestLog_pipelinerun_still_running(t *testing.T) { updatePR(finalPRs, watcher) output, err := fetchLogs(prlo) - if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -3502,7 +3557,6 @@ func TestPipelinerunLog_finally_v1beta1(t *testing.T) { cb.UnstructuredV1beta1PR(prs[0], versionv1beta1), cb.UnstructuredV1beta1P(pps[0], versionv1beta1), ) - if err != nil { t.Errorf("unable to create dynamic client: %v", err) } @@ -3752,7 +3806,6 @@ func TestPipelinerunLog_finally(t *testing.T) { cb.UnstructuredPR(prs[0], version), cb.UnstructuredP(pps[0], version), ) - if err != nil { t.Errorf("unable to create dynamic client: %v", err) } diff --git a/pkg/options/logs.go b/pkg/options/logs.go index 1dce5474f..8e353570b 100644 --- a/pkg/options/logs.go +++ b/pkg/options/logs.go @@ -51,6 +51,7 @@ type LogOptions struct { Tail int64 Timestamps bool Prefixing bool + ExitWithPrError bool // ActivityTimeout is the amount of time to wait for some activity // (e.g. Pod ready) before giving up. ActivityTimeout time.Duration