Skip to content

FromTimeSpan returns 0 for any time span with a day component #434

Description

@Scott-Emberson

FromTimeSpan reads the time span fields at fixed offsets, and takes the day component from timeSpan[0:0], which is always the empty string. Anything carrying a day component parses to zero. Fractional seconds are dropped, and an empty string panics on a slice bound.

pkg/machinepolicies/duration_formatter.go:42 (and the identical copy in pkg/machines/):

days, _ := strconv.ParseInt(timeSpan[0:0], 10, 32)
hours, _ := strconv.ParseInt(timeSpan[2:4], 10, 64)
hours += (days * 24)
minutes, _ := strconv.ParseInt(timeSpan[5:7], 10, 64)
seconds, _ := strconv.ParseInt(timeSpan[8:10], 10, 64)

The fixed offsets also assume a single-digit day, and the server does not pad the day component.

What it returns today

Run against v2.114.1:

FromTimeSpan("00:00:00")       = 0s          (correct)
FromTimeSpan("01:00:00")       = 1h0m0s      (correct)
FromTimeSpan("1.00:00:00")     = 0s          want 24h
FromTimeSpan("02.00:00:00")    = 0s          want 48h
FromTimeSpan("7.12:30:00")     = 12h30m0s    want 180h30m
FromTimeSpan("37500.00:00:00") = 50h0m0s     want 900000h
FromTimeSpan("00:00:00.50000") = 0s          want 500ms
FromTimeSpan("")               = panic: slice bounds out of range [:4] with length 0

Only the hh:mm:ss form works. "1.00:00:00" is the health check interval on the default machine policy, so this is on a common path rather than an edge case.

Why it went unnoticed

pkg/machines/duration_formatter_test.go calls FromTimeSpan seven times and logs each result without asserting anything. Every one of its own inputs returns 0s today and the test passes.

Affected

Any time.Duration field read back through FromTimeSpan. In MachinePolicy alone that is ConnectionConnectTimeout, ConnectionRetrySleepInterval, ConnectionRetryTimeLimit, PollingRequestQueueTimeout and PollingRequestMaximumMessageProcessingTimeout, plus MachineHealthCheckPolicy.HealthCheckInterval and MachineCleanupPolicy.DeleteMachinesElapsedTimeSpan. pkg/machinepolicies/ and pkg/machines/ each hold their own copy of the function.

This also blocks terraform-provider-octopusdeploy#225. The Terraform provider reads a health check interval of one day back as 0, and the fix for that issue makes 0 mean "never run health checks". A policy misparsed as 0 would then be written back to the server as Never, turning health checks off on policies nobody touched.

Suggested fix

Split on the separators instead of slicing at fixed offsets. Both uses of . are ambiguous (d.hh:mm:ss against hh:mm:ss.fffffff), so a leading segment is only the day component when what follows still holds a complete hh:mm:ss. Malformed input should return zero rather than panic.

I have this working in both packages with a table test covering the cases above, and I am happy to open a PR.

Environment

go-octopusdeploy v2.114.1, still present on main.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions