diff --git a/cmd/entrypoint/main.go b/cmd/entrypoint/main.go index dead6fa6581..3935fdd1987 100644 --- a/cmd/entrypoint/main.go +++ b/cmd/entrypoint/main.go @@ -54,6 +54,8 @@ var ( stdoutPath = flag.String("stdout_path", "", "If specified, file to copy stdout to") stderrPath = flag.String("stderr_path", "", "If specified, file to copy stderr to") breakpointOnFailure = flag.Bool("breakpoint_on_failure", false, "If specified, expect steps to not skip on failure") + breakpointBefore = flag.Bool("breakpoint_before", false, "If specified, expect steps to not skip before the specified step") + breakpointAfter = flag.Bool("breakpoint_after", false, "If specified, expect steps to not skip after the specified step") onError = flag.String("on_error", "", "Set to \"continue\" to ignore an error and continue when a container terminates with a non-zero exit code."+ " Set to \"stopAndFail\" to declare a failure with a step error and stop executing the rest of the steps.") stepMetadataDir = flag.String("step_metadata_dir", "", "If specified, create directory to store the step metadata e.g. /tekton/steps//") @@ -67,9 +69,9 @@ const ( breakpointExitSuffix = ".breakpointexit" ) -func checkForBreakpointOnFailure(e entrypoint.Entrypointer, breakpointExitPostFile string) { - if e.BreakpointOnFailure { - if waitErr := e.Waiter.Wait(context.Background(), breakpointExitPostFile, false, false); waitErr != nil { +func checkForBreakpoint(e entrypoint.Entrypointer, breakpointExitPostFile string) { + if e.BreakpointOnFailure || e.BreakpointAfter || e.BreakpointBefore { + if waitErr := e.Waiter.Wait(context.Background(), breakpointExitPostFile, false, e.BreakpointOnFailure); waitErr != nil { log.Println("error occurred while waiting for " + breakpointExitPostFile + " : " + waitErr.Error()) } // get exitcode from .breakpointexit @@ -163,6 +165,8 @@ func main() { StepResults: strings.Split(*stepResults, ","), Timeout: timeout, BreakpointOnFailure: *breakpointOnFailure, + BreakpointAfter: *breakpointAfter, + BreakpointBefore: *breakpointBefore, OnError: *onError, StepMetadataDir: *stepMetadataDir, SpireWorkloadAPI: spireWorkloadAPI, @@ -174,9 +178,10 @@ func main() { if err := credentials.CopyCredsToHome(credentials.CredsInitCredentials); err != nil { log.Printf("non-fatal error copying credentials: %q", err) } - + breakpointExitPostFile := e.PostFile + breakpointExitSuffix + // check if there is a breakpoint set before the entrypoint runs + checkForBreakpoint(e, breakpointExitPostFile) if err := e.Go(); err != nil { - breakpointExitPostFile := e.PostFile + breakpointExitSuffix switch t := err.(type) { //nolint:errorlint // checking for multiple types with errors.As is ugly. case skipError: log.Print("Skipping step because a previous step failed") @@ -201,7 +206,8 @@ func main() { // in both cases has an ExitStatus() method with the // same signature. if status, ok := t.Sys().(syscall.WaitStatus); ok { - checkForBreakpointOnFailure(e, breakpointExitPostFile) + // check if there is a breakpoint set after the command errors out + checkForBreakpoint(e, breakpointExitPostFile) // ignore a step error i.e. do not exit if a container terminates with a non-zero exit code when onError is set to "continue" if e.OnError != entrypoint.ContinueOnError { os.Exit(status.ExitStatus()) @@ -212,7 +218,8 @@ func main() { log.Fatalf("Error executing command (ExitError): %v", err) } default: - checkForBreakpointOnFailure(e, breakpointExitPostFile) + // check if there is a breakpoint set after the command has finished running + checkForBreakpoint(e, breakpointExitPostFile) log.Fatalf("Error executing command: %v", err) } } diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md index f7e0439660c..4bcd08d1ae9 100644 --- a/docs/pipeline-api.md +++ b/docs/pipeline-api.md @@ -4869,6 +4869,26 @@ string failed step will not exit

+ + +afterBreakpoints
+ +[]string + + + + + + + +beforeBreakpoints
+ +[]string + + + + +

TaskKind @@ -13957,6 +13977,28 @@ string failed step will not exit

+ + +beforeSteps
+ +[]string + + + +(Optional) + + + + +afterSteps
+ +[]string + + + +(Optional) + +

TaskKind diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go index c6a73d7afc3..9ffc10c796f 100644 --- a/pkg/apis/pipeline/v1/openapi_generated.go +++ b/pkg/apis/pipeline/v1/openapi_generated.go @@ -3377,6 +3377,34 @@ func schema_pkg_apis_pipeline_v1_TaskBreakpoints(ref common.ReferenceCallback) c Format: "", }, }, + "afterBreakpoints": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "beforeBreakpoints": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json index 1424609e3b6..9ac1d97303e 100644 --- a/pkg/apis/pipeline/v1/swagger.json +++ b/pkg/apis/pipeline/v1/swagger.json @@ -1729,6 +1729,20 @@ "description": "TaskBreakpoints defines the breakpoint config for a particular Task", "type": "object", "properties": { + "afterBreakpoints": { + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "beforeBreakpoints": { + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, "onFailure": { "description": "if enabled, pause TaskRun on failure of a step failed step will not exit", "type": "string" diff --git a/pkg/apis/pipeline/v1/taskrun_types.go b/pkg/apis/pipeline/v1/taskrun_types.go index ff94ed6c330..96fd9948e06 100644 --- a/pkg/apis/pipeline/v1/taskrun_types.go +++ b/pkg/apis/pipeline/v1/taskrun_types.go @@ -16,6 +16,7 @@ package v1 import ( "context" "fmt" + "k8s.io/utils/strings/slices" "time" "github.com/tektoncd/pipeline/pkg/apis/config" @@ -119,7 +120,9 @@ type TaskBreakpoints struct { // if enabled, pause TaskRun on failure of a step // failed step will not exit // +optional - OnFailure string `json:"onFailure,omitempty"` + OnFailure string `json:"onFailure,omitempty"` + AfterBreakpoints []string `json:"afterBreakpoints,omitempty"` + BeforeBreakpoints []string `json:"beforeBreakpoints,omitempty"` } // NeedsDebugOnFailure return true if the TaskRun is configured to debug on failure @@ -130,14 +133,19 @@ func (trd *TaskRunDebug) NeedsDebugOnFailure() bool { return trd.Breakpoints.OnFailure == EnabledOnFailureBreakpoint } -// StepNeedsDebug return true if the step is configured to debug -func (trd *TaskRunDebug) StepNeedsDebug(stepName string) bool { - return trd.NeedsDebugOnFailure() +// StepNeedsDebugAfter return true if the step is configured to have a breakpoint after it's execution +func (trd *TaskRunDebug) StepNeedsDebugAfter(stepName string) bool { + return slices.Contains(trd.Breakpoints.AfterBreakpoints, stepName) +} + +// StepNeedsDebugBefore return true if the step is configured to have a breakpoint before it's execution +func (trd *TaskRunDebug) StepNeedsDebugBefore(stepName string) bool { + return slices.Contains(trd.Breakpoints.BeforeBreakpoints, stepName) } // NeedsDebug return true if defined onfailure or have any before, after steps func (trd *TaskRunDebug) NeedsDebug() bool { - return trd.NeedsDebugOnFailure() + return trd.NeedsDebugOnFailure() || len(trd.Breakpoints.AfterBreakpoints) > 0 || len(trd.Breakpoints.BeforeBreakpoints) > 0 } // TaskRunInputs holds the input values that this task was invoked with. diff --git a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go index db26c7d4c5e..4078482a71b 100644 --- a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go @@ -1473,6 +1473,16 @@ func (in *Task) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TaskBreakpoints) DeepCopyInto(out *TaskBreakpoints) { *out = *in + if in.AfterBreakpoints != nil { + in, out := &in.AfterBreakpoints, &out.AfterBreakpoints + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.BeforeBreakpoints != nil { + in, out := &in.BeforeBreakpoints, &out.BeforeBreakpoints + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1598,7 +1608,7 @@ func (in *TaskRunDebug) DeepCopyInto(out *TaskRunDebug) { if in.Breakpoints != nil { in, out := &in.Breakpoints, &out.Breakpoints *out = new(TaskBreakpoints) - **out = **in + (*in).DeepCopyInto(*out) } return } diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go index c55315ac140..8a62066d6d8 100644 --- a/pkg/apis/pipeline/v1beta1/openapi_generated.go +++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go @@ -4370,6 +4370,34 @@ func schema_pkg_apis_pipeline_v1beta1_TaskBreakpoints(ref common.ReferenceCallba Format: "", }, }, + "beforeSteps": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "afterSteps": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json index b8624cf62ce..bc63a05a64e 100644 --- a/pkg/apis/pipeline/v1beta1/swagger.json +++ b/pkg/apis/pipeline/v1beta1/swagger.json @@ -2413,6 +2413,20 @@ "description": "TaskBreakpoints defines the breakpoint config for a particular Task", "type": "object", "properties": { + "afterSteps": { + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "beforeSteps": { + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, "onFailure": { "description": "if enabled, pause TaskRun on failure of a step failed step will not exit", "type": "string" diff --git a/pkg/apis/pipeline/v1beta1/taskrun_types.go b/pkg/apis/pipeline/v1beta1/taskrun_types.go index a12676acb0a..79a0db20df7 100644 --- a/pkg/apis/pipeline/v1beta1/taskrun_types.go +++ b/pkg/apis/pipeline/v1beta1/taskrun_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( "context" "fmt" + "k8s.io/utils/strings/slices" "time" "github.com/tektoncd/pipeline/pkg/apis/config" @@ -125,6 +126,10 @@ type TaskBreakpoints struct { // failed step will not exit // +optional OnFailure string `json:"onFailure,omitempty"` + // +optional + BeforeSteps []string `json:"beforeSteps,omitempty"` + // +optional + AfterSteps []string `json:"afterSteps,omitempty"` } // NeedsDebugOnFailure return true if the TaskRun is configured to debug on failure @@ -137,12 +142,12 @@ func (trd *TaskRunDebug) NeedsDebugOnFailure() bool { // StepNeedsDebug return true if the step is configured to debug func (trd *TaskRunDebug) StepNeedsDebug(stepName string) bool { - return trd.NeedsDebugOnFailure() + return trd.NeedsDebugOnFailure() || slices.Contains(trd.Breakpoints.BeforeSteps, stepName) || slices.Contains(trd.Breakpoints.AfterSteps, stepName) } // NeedsDebug return true if defined onfailure or have any before, after steps func (trd *TaskRunDebug) NeedsDebug() bool { - return trd.NeedsDebugOnFailure() + return trd.NeedsDebugOnFailure() || len(trd.Breakpoints.BeforeSteps) > 0 || len(trd.Breakpoints.AfterSteps) > 0 } var taskRunCondSet = apis.NewBatchConditionSet() diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go index 68cce2d2643..e0e0c91ffa8 100644 --- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go @@ -1948,6 +1948,16 @@ func (in *Task) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TaskBreakpoints) DeepCopyInto(out *TaskBreakpoints) { *out = *in + if in.BeforeSteps != nil { + in, out := &in.BeforeSteps, &out.BeforeSteps + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AfterSteps != nil { + in, out := &in.AfterSteps, &out.AfterSteps + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -2138,7 +2148,7 @@ func (in *TaskRunDebug) DeepCopyInto(out *TaskRunDebug) { if in.Breakpoints != nil { in, out := &in.Breakpoints, &out.Breakpoints *out = new(TaskBreakpoints) - **out = **in + (*in).DeepCopyInto(*out) } return } diff --git a/pkg/entrypoint/entrypointer.go b/pkg/entrypoint/entrypointer.go index d27d3554682..d7d331fe253 100644 --- a/pkg/entrypoint/entrypointer.go +++ b/pkg/entrypoint/entrypointer.go @@ -106,6 +106,10 @@ type Entrypointer struct { Timeout *time.Duration // BreakpointOnFailure helps determine if entrypoint execution needs to adapt debugging requirements BreakpointOnFailure bool + // BreakpointAfter helps determine if entrypoint execution needs to halt after for debugging + BreakpointAfter bool + // BreakpointBefore helps determine if entrypoint execution needs to halt before for debugging + BreakpointBefore bool // OnError defines exiting behavior of the entrypoint // set it to "stopAndFail" to indicate the entrypoint to exit the taskRun if the container exits with non zero exit code // set it to "continue" to indicate the entrypoint to continue executing the rest of the steps irrespective of the container exit code diff --git a/pkg/pod/entrypoint.go b/pkg/pod/entrypoint.go index f8c73d7491b..e3d54634e4d 100644 --- a/pkg/pod/entrypoint.go +++ b/pkg/pod/entrypoint.go @@ -179,6 +179,12 @@ func orderContainers(ctx context.Context, commonExtraEntrypointArgs []string, st if breakpointConfig != nil && breakpointConfig.NeedsDebugOnFailure() { argsForEntrypoint = append(argsForEntrypoint, "-breakpoint_on_failure") } + if breakpointConfig != nil && breakpointConfig.StepNeedsDebugAfter(s.Name) { + argsForEntrypoint = append(argsForEntrypoint, "-breakpoint_after") + } + if breakpointConfig != nil && breakpointConfig.StepNeedsDebugBefore(s.Name) { + argsForEntrypoint = append(argsForEntrypoint, "-breakpoint_before") + } cmd, args := s.Command, s.Args if len(cmd) > 0 { diff --git a/pkg/pod/script.go b/pkg/pod/script.go index 32ee47ae2e6..fe1a1ca39e8 100644 --- a/pkg/pod/script.go +++ b/pkg/pod/script.go @@ -145,7 +145,7 @@ func convertListOfSteps(steps []v1.Step, initContainer *corev1.Container, debugC } containers = append(containers, *c) } - if debugConfig != nil && debugConfig.NeedsDebugOnFailure() { + if debugConfig != nil && debugConfig.NeedsDebug() { placeDebugScriptInContainers(containers, initContainer) } return containers