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
2 changes: 2 additions & 0 deletions PRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ Each template fixes its deployment revision, image digest, command, resources an

The worker command must read `/run/hakopod/invocation/input.json` directly. Use a payload-file-enabled Rover image. Flowr and connector workers remain unsupported by this provider until their own file-input contracts are qualified. Keep `APOLLO_CONNECTORS_ENABLED=false`.

Database access belongs to the job service, not Apollo's request: reference the DSN as an application secret (`secrets.DATABASE_URL = { ref = "..." }`) and reach the database through `private_egress` (an administrator-approved destination) or a shared internal network. A failed invocation's error carries the container exit code when Hakopod reports one, for example `Hakopod invocation failed: exit code 3`; its bounded logs are returned with it.

Run one Apollo scheduler replica. Hakopod dispatch, cancellation, cleanup and bounded terminal logs use its durable invocation API. Lost submission acknowledgements are looked up by idempotency key; Apollo does not create a replacement job. A cancellation that cannot be confirmed is returned as an error.
4 changes: 4 additions & 0 deletions runner/hakopod.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type invocationRecord struct {
CorrelationID string `json:"correlation_id"`
Status string `json:"status"`
CleanupPending bool `json:"cleanup_pending"`
ExitCode *int32 `json:"exit_code"`
}

type invocationCreate struct {
Expand Down Expand Up @@ -307,6 +308,9 @@ func (r *HakopodRunner) RunJob(ctx context.Context, prefix string, req JobReques
logs.Text += "\n[worker logs truncated]"
}
if current.Status != "succeeded" {
if current.ExitCode != nil {
return logs.Text, fmt.Errorf("Hakopod invocation %s: exit code %d", current.Status, *current.ExitCode)
}
return logs.Text, fmt.Errorf("Hakopod invocation %s", current.Status)
}
return logs.Text, nil
Expand Down
19 changes: 19 additions & 0 deletions runner/hakopod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,22 @@ func TestHakopodAliasUsesPinnedImageAndFixedResourceCeiling(t *testing.T) {
t.Fatal(err)
}
}

func TestHakopodReportsExitCodeWithLogs(t *testing.T) {
var r *HakopodRunner
var req JobRequest
r, req = newHakopodHarness(t, func(w http.ResponseWriter, h *http.Request) {
if strings.HasSuffix(h.URL.Path, "/logs") {
json.NewEncoder(w).Encode(map[string]any{"text": "boom", "truncated": false})
return
}
value := testInvocation(r, req, "failed")
code := int32(3)
value.ExitCode = &code
json.NewEncoder(w).Encode(value)
})
logs, err := r.RunJob(context.Background(), req.Prefix, req)
if logs != "boom" || err == nil || err.Error() != "Hakopod invocation failed: exit code 3" {
t.Fatalf("unexpected result %q %v", logs, err)
}
}
Loading