Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions cmd/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ runtime checks are also run and merged into the report.
All flags not consumed by sitectl itself are forwarded to the plugin's
healthcheck handler, allowing plugin-specific flags such as --codebase-rootfs.

Exits non-zero if any check fails after the timeout.
By default, healthcheck runs once, prints the current status of each check, and
exits non-zero if any check fails. Use --persist to keep retrying until all
checks pass or --timeout is reached.

Examples:
sitectl healthcheck
sitectl healthcheck --timeout 10m --interval 15s
sitectl healthcheck --persist --timeout 10m --interval 15s
sitectl healthcheck --format table`,
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -41,7 +43,7 @@ Examples:
if err != nil {
return err
}
if hostParams.Interval <= 0 {
if hostParams.Persist && hostParams.Interval <= 0 {
return fmt.Errorf("--interval must be greater than zero")
}

Expand All @@ -51,7 +53,7 @@ Examples:
}
ctx := &ctxVal

report, err := runHealthcheckUntilHealthy(cmd, ctx, contextName, hostParams, healthcheckParams, pluginArgs)
report, err := runHealthcheckReport(cmd, ctx, contextName, hostParams, healthcheckParams, pluginArgs)
if err != nil {
return err
}
Expand All @@ -70,6 +72,19 @@ func init() {
RootCmd.AddCommand(healthcheckCmd)
}

func runHealthcheckReport(cmd *cobra.Command, ctx *config.Context, contextName string, hostParams healthcheckHostParams, healthcheckParams plugin.HealthcheckRunParams, pluginArgs []string) (sitevalidate.Report, error) {
if hostParams.Persist {
return runHealthcheckUntilHealthy(cmd, ctx, contextName, hostParams, healthcheckParams, pluginArgs)
}

results, err := runHealthcheckOnce(cmd, ctx, contextName, healthcheckParams, pluginArgs)
if err != nil {
return sitevalidate.Report{}, err
}
sitevalidate.SortResults(results)
return sitevalidate.NewReport(ctx, results), nil
}

func runHealthcheckUntilHealthy(cmd *cobra.Command, ctx *config.Context, contextName string, hostParams healthcheckHostParams, healthcheckParams plugin.HealthcheckRunParams, pluginArgs []string) (sitevalidate.Report, error) {
deadline := time.Now().Add(hostParams.Timeout)
var last sitevalidate.Report
Expand Down
12 changes: 12 additions & 0 deletions cmd/rpc_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -30,6 +31,7 @@ func extractValidateRPCParams(args []string) (string, plugin.ValidateRunParams,

type healthcheckHostParams struct {
Format string
Persist bool
Timeout time.Duration
Interval time.Duration
}
Expand Down Expand Up @@ -82,6 +84,10 @@ func extractHealthcheckHostParams(args []string) (healthcheckHostParams, []strin
i++
value = args[i]
}
case "persist":
if !hasValue {
value = "true"
}
default:
passthrough = append(passthrough, arg)
continue
Expand All @@ -90,6 +96,12 @@ func extractHealthcheckHostParams(args []string) (healthcheckHostParams, []strin
switch name {
case "format":
params.Format = value
case "persist":
persist, err := strconv.ParseBool(value)
if err != nil {
return healthcheckHostParams{}, nil, fmt.Errorf("parse --persist: %w", err)
}
params.Persist = persist
case "timeout":
timeout, err := time.ParseDuration(value)
if err != nil {
Expand Down
60 changes: 60 additions & 0 deletions cmd/rpc_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strings"
"testing"
"time"

"github.com/libops/sitectl/pkg/config"
"github.com/libops/sitectl/pkg/plugin"
Expand Down Expand Up @@ -83,6 +84,65 @@ func TestExtractValidateRPCParamsPromotesReportFormatAndRootfs(t *testing.T) {
}
}

func TestExtractHealthcheckRPCParamsPromotesHostFlags(t *testing.T) {
host, _, passthrough, err := extractHealthcheckRPCParams([]string{
"--format", "json",
"--persist",
"--timeout=2m",
"--interval", "5s",
"--codebase-rootfs", "app/rootfs",
"--plugin-flag",
})
if err != nil {
t.Fatalf("extractHealthcheckRPCParams() error = %v", err)
}

if host.Format != "json" {
t.Fatalf("Format = %q, want json", host.Format)
}
if !host.Persist {
t.Fatal("Persist = false, want true")
}
if host.Timeout != 2*time.Minute {
t.Fatalf("Timeout = %s, want 2m", host.Timeout)
}
if host.Interval != 5*time.Second {
t.Fatalf("Interval = %s, want 5s", host.Interval)
}
wantPassthrough := []string{"--codebase-rootfs", "app/rootfs", "--plugin-flag"}
if !reflect.DeepEqual(passthrough, wantPassthrough) {
t.Fatalf("passthrough = %#v, want %#v", passthrough, wantPassthrough)
}
}

func TestExtractHealthcheckRPCParamsDefaultsToOneShot(t *testing.T) {
host, _, passthrough, err := extractHealthcheckRPCParams([]string{
"--persist=false",
"--custom",
})
if err != nil {
t.Fatalf("extractHealthcheckRPCParams() error = %v", err)
}

if host.Persist {
t.Fatal("Persist = true, want false")
}
wantPassthrough := []string{"--custom"}
if !reflect.DeepEqual(passthrough, wantPassthrough) {
t.Fatalf("passthrough = %#v, want %#v", passthrough, wantPassthrough)
}
}

func TestExtractHealthcheckRPCParamsRejectsInvalidPersist(t *testing.T) {
_, _, _, err := extractHealthcheckRPCParams([]string{"--persist=maybe"})
if err == nil {
t.Fatal("expected invalid persist error")
}
if !strings.Contains(err.Error(), "parse --persist") {
t.Fatalf("unexpected error: %v", err)
}
}

func TestExtractComponentSetRPCParamsSeparatesTargetAndPassthrough(t *testing.T) {
params, passthrough, err := extractComponentSetRPCParams([]string{
"--path", "/srv/site",
Expand Down