Skip to content

Commit affe6c5

Browse files
committed
fix: change type *int32 to string
Signed-off-by: Oleksii Kurinnyi <[email protected]>
1 parent 14629eb commit affe6c5

File tree

6 files changed

+80
-23
lines changed

6 files changed

+80
-23
lines changed

apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,10 @@ type WorkspaceConfig struct {
192192
// PostStartTimeout defines the maximum duration the PostStart hook can run
193193
// before it is automatically failed. This timeout is used for the postStart lifecycle hook
194194
// that is used to run commands in the workspace container. The timeout is specified in seconds.
195-
// If not specified, the timeout is disabled (0 seconds).
196-
// +kubebuilder:validation:Minimum=0
195+
// Duration should be specified in a format parseable by Go's time package, e.g. "20s", "2m".
196+
// If not specified or "0", the timeout is disabled.
197197
// +kubebuilder:validation:Optional
198-
// +kubebuilder:validation:Type=integer
199-
// +kubebuilder:validation:Format=int32
200-
PostStartTimeout *int32 `json:"postStartTimeout,omitempty"`
198+
PostStartTimeout string `json:"postStartTimeout,omitempty"`
201199
}
202200

203201
type WebhookConfig struct {

pkg/config/sync.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
432432
}
433433
}
434434

435-
if from.Workspace.PostStartTimeout != nil {
435+
if from.Workspace.PostStartTimeout != "" {
436436
to.Workspace.PostStartTimeout = from.Workspace.PostStartTimeout
437437
}
438438
}
@@ -605,8 +605,8 @@ func GetCurrentConfigString(currConfig *controller.OperatorConfiguration) string
605605
if workspace.IdleTimeout != defaultConfig.Workspace.IdleTimeout {
606606
config = append(config, fmt.Sprintf("workspace.idleTimeout=%s", workspace.IdleTimeout))
607607
}
608-
if workspace.PostStartTimeout != nil && workspace.PostStartTimeout != defaultConfig.Workspace.PostStartTimeout {
609-
config = append(config, fmt.Sprintf("workspace.postStartTimeout=%d", *workspace.PostStartTimeout))
608+
if workspace.PostStartTimeout != defaultConfig.Workspace.PostStartTimeout {
609+
config = append(config, fmt.Sprintf("workspace.postStartTimeout=%s", workspace.PostStartTimeout))
610610
}
611611
if workspace.ProgressTimeout != "" && workspace.ProgressTimeout != defaultConfig.Workspace.ProgressTimeout {
612612
config = append(config, fmt.Sprintf("workspace.progressTimeout=%s", workspace.ProgressTimeout))

pkg/library/container/container.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import (
4545
// rewritten as Volumes are added to PodAdditions, in order to support e.g. using one PVC to hold all volumes
4646
//
4747
// Note: Requires DevWorkspace to be flattened (i.e. the DevWorkspace contains no Parent or Components of type Plugin)
48-
func GetKubeContainersFromDevfile(workspace *dw.DevWorkspaceTemplateSpec, securityContext *corev1.SecurityContext, pullPolicy string, defaultResources *corev1.ResourceRequirements, postStartTimeout *int32) (*v1alpha1.PodAdditions, error) {
48+
func GetKubeContainersFromDevfile(workspace *dw.DevWorkspaceTemplateSpec, securityContext *corev1.SecurityContext, pullPolicy string, defaultResources *corev1.ResourceRequirements, postStartTimeout string) (*v1alpha1.PodAdditions, error) {
4949
if !flatten.DevWorkspaceIsFlattened(workspace, nil) {
5050
return nil, fmt.Errorf("devfile is not flattened")
5151
}

pkg/library/container/container_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestGetKubeContainersFromDevfile(t *testing.T) {
8787
t.Run(tt.Name, func(t *testing.T) {
8888
// sanity check that file is read correctly.
8989
assert.True(t, len(tt.Input.Components) > 0, "Input defines no components")
90-
gotPodAdditions, err := GetKubeContainersFromDevfile(tt.Input, nil, testImagePullPolicy, defaultResources, nil)
90+
gotPodAdditions, err := GetKubeContainersFromDevfile(tt.Input, nil, testImagePullPolicy, defaultResources, "")
9191
if tt.Output.ErrRegexp != nil && assert.Error(t, err) {
9292
assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match")
9393
} else {

pkg/library/lifecycle/poststart.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ package lifecycle
1616
import (
1717
"fmt"
1818
"strings"
19+
"time"
1920

2021
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
2122
corev1 "k8s.io/api/core/v1"
23+
"sigs.k8s.io/controller-runtime/pkg/log"
2224
)
2325

2426
const (
@@ -39,7 +41,7 @@ const (
3941
`
4042
)
4143

42-
func AddPostStartLifecycleHooks(wksp *dw.DevWorkspaceTemplateSpec, containers []corev1.Container, postStartTimeout *int32) error {
44+
func AddPostStartLifecycleHooks(wksp *dw.DevWorkspaceTemplateSpec, containers []corev1.Container, postStartTimeout string) error {
4345
if wksp.Events == nil || len(wksp.Events.PostStart) == 0 {
4446
return nil
4547
}
@@ -83,8 +85,8 @@ func AddPostStartLifecycleHooks(wksp *dw.DevWorkspaceTemplateSpec, containers []
8385

8486
// processCommandsForPostStart processes a list of DevWorkspace commands
8587
// and generates a corev1.LifecycleHandler for the PostStart lifecycle hook.
86-
func processCommandsForPostStart(commands []dw.Command, postStartTimeout *int32) (*corev1.LifecycleHandler, error) {
87-
if postStartTimeout == nil || *postStartTimeout == 0 {
88+
func processCommandsForPostStart(commands []dw.Command, postStartTimeout string) (*corev1.LifecycleHandler, error) {
89+
if postStartTimeout == "" {
8890
// use the fallback if no timeout propagated
8991
return processCommandsWithoutTimeoutFallback(commands)
9092
}
@@ -99,7 +101,7 @@ func processCommandsForPostStart(commands []dw.Command, postStartTimeout *int32)
99101
scriptToExecute := "set -e\n" + originalUserScript
100102
escapedUserScriptForTimeoutWrapper := strings.ReplaceAll(scriptToExecute, "'", `'\''`)
101103

102-
fullScriptWithTimeout := generateScriptWithTimeout(escapedUserScriptForTimeoutWrapper, *postStartTimeout)
104+
fullScriptWithTimeout := generateScriptWithTimeout(escapedUserScriptForTimeoutWrapper, postStartTimeout)
103105

104106
finalScriptForHook := fmt.Sprintf(redirectOutputFmt, fullScriptWithTimeout)
105107

@@ -185,7 +187,19 @@ func buildUserScript(commands []dw.Command) (string, error) {
185187
// environment variable exports, and specific exit code handling.
186188
// The killAfterDurationSeconds is hardcoded to 5s within this generated script.
187189
// It conditionally prefixes the user script with the timeout command if available.
188-
func generateScriptWithTimeout(escapedUserScript string, timeoutSeconds int32) string {
190+
func generateScriptWithTimeout(escapedUserScript string, postStartTimeout string) string {
191+
// Convert `postStartTimeout` into the `timeout` format
192+
var timeoutSeconds int64
193+
if postStartTimeout != "" && postStartTimeout != "0" {
194+
duration, err := time.ParseDuration(postStartTimeout)
195+
if err != nil {
196+
log.Log.Error(err, "Could not parse post-start timeout, disabling timeout", "value", postStartTimeout)
197+
timeoutSeconds = 0
198+
} else {
199+
timeoutSeconds = int64(duration.Seconds())
200+
}
201+
}
202+
189203
return fmt.Sprintf(`
190204
export POSTSTART_TIMEOUT_DURATION="%d"
191205
export POSTSTART_KILL_AFTER_DURATION="5"

pkg/library/lifecycle/poststart_test.go

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ func TestAddPostStartLifecycleHooks(t *testing.T) {
7575
tests := loadAllPostStartTestCasesOrPanic(t, "./testdata/postStart")
7676
for _, tt := range tests {
7777
t.Run(fmt.Sprintf("%s (%s)", tt.Name, tt.testPath), func(t *testing.T) {
78-
var timeout int32
79-
err := AddPostStartLifecycleHooks(tt.Input.Devfile, tt.Input.Containers, &timeout)
78+
var timeout string
79+
err := AddPostStartLifecycleHooks(tt.Input.Devfile, tt.Input.Containers, timeout)
8080
if tt.Output.ErrRegexp != nil && assert.Error(t, err) {
8181
assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match")
8282
} else {
@@ -298,13 +298,13 @@ func TestGenerateScriptWithTimeout(t *testing.T) {
298298
tests := []struct {
299299
name string
300300
escapedUserScript string
301-
timeoutSeconds int32
301+
timeout string
302302
expectedScript string
303303
}{
304304
{
305305
name: "Basic script with timeout",
306306
escapedUserScript: "echo 'hello world'\nsleep 1",
307-
timeoutSeconds: 10,
307+
timeout: "10s",
308308
expectedScript: `
309309
export POSTSTART_TIMEOUT_DURATION="10"
310310
export POSTSTART_KILL_AFTER_DURATION="5"
@@ -350,7 +350,7 @@ exit $exit_code
350350
{
351351
name: "Script with zero timeout (no timeout)",
352352
escapedUserScript: "echo 'running indefinitely...'",
353-
timeoutSeconds: 0,
353+
timeout: "0s",
354354
expectedScript: `
355355
export POSTSTART_TIMEOUT_DURATION="0"
356356
export POSTSTART_KILL_AFTER_DURATION="5"
@@ -395,7 +395,7 @@ exit $exit_code
395395
{
396396
name: "Empty user script",
397397
escapedUserScript: "",
398-
timeoutSeconds: 5,
398+
timeout: "5s",
399399
expectedScript: `
400400
export POSTSTART_TIMEOUT_DURATION="5"
401401
export POSTSTART_KILL_AFTER_DURATION="5"
@@ -440,7 +440,7 @@ exit $exit_code
440440
{
441441
name: "User script with already escaped single quotes",
442442
escapedUserScript: "echo 'it'\\''s complex'",
443-
timeoutSeconds: 30,
443+
timeout: "30s",
444444
expectedScript: `
445445
export POSTSTART_TIMEOUT_DURATION="30"
446446
export POSTSTART_KILL_AFTER_DURATION="5"
@@ -479,14 +479,59 @@ else
479479
fi
480480
fi
481481
482+
exit $exit_code
483+
`,
484+
},
485+
{
486+
name: "User script with minute timeout",
487+
escapedUserScript: "echo 'wait for it...'",
488+
timeout: "2m",
489+
expectedScript: `
490+
export POSTSTART_TIMEOUT_DURATION="120"
491+
export POSTSTART_KILL_AFTER_DURATION="5"
492+
493+
_TIMEOUT_COMMAND_PART=""
494+
_WAS_TIMEOUT_USED="false" # Use strings "true" or "false" for shell boolean
495+
496+
if command -v timeout >/dev/null 2>&1; then
497+
echo "[postStart hook] Executing commands with timeout: ${POSTSTART_TIMEOUT_DURATION} seconds, kill after: ${POSTSTART_KILL_AFTER_DURATION} seconds" >&2
498+
_TIMEOUT_COMMAND_PART="timeout --preserve-status --kill-after=${POSTSTART_KILL_AFTER_DURATION} ${POSTSTART_TIMEOUT_DURATION}"
499+
_WAS_TIMEOUT_USED="true"
500+
else
501+
echo "[postStart hook] WARNING: 'timeout' utility not found. Executing commands without timeout." >&2
502+
fi
503+
504+
# Execute the user's script
505+
${_TIMEOUT_COMMAND_PART} /bin/sh -c 'echo 'wait for it...''
506+
exit_code=$?
507+
508+
# Check the exit code based on whether timeout was attempted
509+
if [ "$_WAS_TIMEOUT_USED" = "true" ]; then
510+
if [ $exit_code -eq 143 ]; then # 128 + 15 (SIGTERM)
511+
echo "[postStart hook] Commands terminated by SIGTERM (likely timed out after ${POSTSTART_TIMEOUT_DURATION}s). Exit code 143." >&2
512+
elif [ $exit_code -eq 137 ]; then # 128 + 9 (SIGKILL)
513+
echo "[postStart hook] Commands forcefully killed by SIGKILL (likely after --kill-after ${POSTSTART_KILL_AFTER_DURATION}s expired). Exit code 137." >&2
514+
elif [ $exit_code -ne 0 ]; then # Catches any other non-zero exit code
515+
echo "[postStart hook] Commands failed with exit code $exit_code." >&2
516+
else
517+
echo "[postStart hook] Commands completed successfully within the time limit." >&2
518+
fi
519+
else
520+
if [ $exit_code -ne 0 ]; then
521+
echo "[postStart hook] Commands failed with exit code $exit_code (no timeout)." >&2
522+
else
523+
echo "[postStart hook] Commands completed successfully (no timeout)." >&2
524+
fi
525+
fi
526+
482527
exit $exit_code
483528
`,
484529
},
485530
}
486531

487532
for _, tt := range tests {
488533
t.Run(tt.name, func(t *testing.T) {
489-
script := generateScriptWithTimeout(tt.escapedUserScript, tt.timeoutSeconds)
534+
script := generateScriptWithTimeout(tt.escapedUserScript, tt.timeout)
490535
assert.Equal(t, tt.expectedScript, script)
491536
})
492537
}

0 commit comments

Comments
 (0)