Skip to content

feat: allow a machine health check schedule of Never - #436

Open
Scott-Emberson wants to merge 2 commits into
OctopusDeploy:mainfrom
Scott-Emberson:feat/health-check-interval-never
Open

feat: allow a machine health check schedule of Never#436
Scott-Emberson wants to merge 2 commits into
OctopusDeploy:mainfrom
Scott-Emberson:feat/health-check-interval-never

Conversation

@Scott-Emberson

Copy link
Copy Markdown

Unblocks OctopusDeploy/terraform-provider-octopusdeploy#225.

Stacked on #435, which needs to merge first. GitHub will not let me base a pull request from a fork on another fork branch, so the diff here shows both commits. Only the second one, feat: allow a machine health check schedule of Never, is new.

The problem

A health check schedule of "Never" is the absence of both HealthCheckInterval and HealthCheckCron. There is no schedule type on the wire; the portal derives it:

scheduleType = MachineHealthCheckPolicy.HealthCheckInterval ? Interval
             : MachineHealthCheckPolicy.HealthCheckCron     ? Cron
             : None

MarshalJSON always populated the interval through ToTimeSpan, and ToTimeSpan(0) returns "00:00:00". That is not an empty string, so the omitempty tag on the wire struct never fired. No value of HealthCheckInterval could produce a payload without the field, which left "Never" unreachable through this SDK, and "00:00:00" reads back as an interval of zero rather than as Never.

The change

Write the interval only when it is non-zero. UnmarshalJSON already guards with if len(fields.HealthCheckInterval) > 0, so a null from the server leaves the field at zero and the round trip is symmetric.

HealthCheckInterval stays a time.Duration. Making it a pointer would express the same thing but break every caller that assigns to it.

Applied to pkg/machinepolicies/ and pkg/machines/, which each carry their own copy.

Why it depends on the other commit

Before the FromTimeSpan fix, a policy whose interval carries a day component reads back as zero. Landing this change on its own would mean those policies get written back with the interval omitted, which switches them to Never. "1.00:00:00" is the default machine policy's interval, so that would be most of them.

Tests

Unit tests covering the omission at zero, the interval still being written when non-zero, and an absent interval deserialising to zero.

Verified against a 2026.x server through machinepolicies.Add and machinepolicies.Update: creating a policy with a zero interval stores HealthCheckInterval: null, re-reading gives 0s, and updating a seven day policy to zero also lands as null. Reading the raw payload back confirms the field is absent rather than "00:00:00", so the portal shows the schedule as Never.

Scott-Emberson and others added 2 commits August 3, 2026 12:57
FromTimeSpan read the time span fields at fixed offsets and took the day
component from timeSpan[0:0], which is always the empty string. Every value
carrying a day component therefore parsed to zero, including "1.00:00:00" —
the interval on the default machine policy — and any fractional seconds were
dropped. An empty string panicked on a slice bound.

Parse the components by separator instead. The day and fractional-second
parts are both optional, and the server does not pad the day component to a
fixed width, so offsets cannot be assumed. Malformed input now yields a zero
duration rather than a panic.

The existing tests only logged their results and asserted nothing, which is
why this went unnoticed; they now assert, and every case they already covered
was returning zero.

Closes OctopusDeploy#434

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Octopus server represents a health check schedule of "Never" as the
absence of both HealthCheckInterval and HealthCheckCron; there is no schedule
type on the wire. MarshalJSON always populated HealthCheckInterval through
ToTimeSpan, and ToTimeSpan(0) is "00:00:00", which is not empty and so
defeated the omitempty tag. No value of HealthCheckInterval could produce a
payload without the field, leaving "Never" unreachable through this SDK.

Write the interval only when it is non-zero, so a zero interval is omitted
and the server stores null. UnmarshalJSON already leaves the field at zero
when the server sends null, so the round trip is symmetric.

Depends on the FromTimeSpan fix in the preceding commit: without it a policy
whose interval carries a day component reads back as zero, and would then be
written back as Never.

Unblocks OctopusDeploy/terraform-provider-octopusdeploy#225

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant