package rollout import ( "fmt" "strings" "time" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/rolloutwords" "github.com/carlosframework/platform/internal/status" ) // EventCode is a rollout event's stable internal name (spec §9.4). The codes // are what the journal and tests key on. The member-facing texts below are // worded by internal/rolloutwords, and are approved copy. type EventCode string const ( EvSwapping EventCode = "swap.started" EvSwapped EventCode = "swap.done" EvSwapFailed EventCode = "swap.failed" EvSwapDown EventCode = "swap.down" EvCohortHalted EventCode = "cohort.halted" EvFallbackServed EventCode = "fallback.served" EvConfigPending EventCode = "config.pending" EvStagingRefused EventCode = "staging.refused" ) // reasonWord turns an attempt's diagnostic text (SwapResult.Reason, an error) // into one of status's reason words (status.Reason*, the one vocabulary the // events and the status report share), so no free text, which can name paths // and internals, reaches a member-facing surface. The raw text goes to the // operator log instead. Anything unrecognised is a failed health check: the // swap's checks are the health check, the settle and the exit watch, and the // health check is the one a spawn or start error also fails. func reasonWord(text string) string { t := strings.ToLower(text) switch { case strings.Contains(t, "reused"): return status.ReasonReused case strings.Contains(t, "staging"), strings.Contains(t, "staged"), strings.Contains(t, "digest"): return status.ReasonStaging case strings.Contains(t, "settle"): return status.ReasonSettle case strings.Contains(t, "exited"): return status.ReasonExit } return status.ReasonHealth } func textSwapping(build string, cause activator.Cause) string { return fmt.Sprintf(rolloutwords.EventSwapping, build, rolloutwords.Cause(string(cause))) } // textSwapped carries the retiring generation's drain time (spec §9.4), // printed to a tenth of a second. func textSwapped(build string, drain time.Duration) string { return fmt.Sprintf(rolloutwords.EventSwapped, build, drain.Round(100*time.Millisecond).String()) } // textSwapFailed's reason is a reason word (reasonWord at the emit site), // never raw text. It is worded as it is given: mapping it again would fold // the closed words into one another (reasonWord("exit") is a health // failure). func textSwapFailed(build, serving, reason string) string { return fmt.Sprintf(rolloutwords.EventSwapFailed, build, rolloutwords.Reason(reason), serving) } func textSwapDown(build string) string { return fmt.Sprintf(rolloutwords.EventHoldDown, build) } // textCohortHalted names the box (spec §9.4); the event sink already // carries the account, the app and the host. reason is a reason word. func textCohortHalted(build, box, reason string) string { return fmt.Sprintf(rolloutwords.EventCohortHalted, build, box, rolloutwords.Reason(reason)) } func textFallback(failed, good string) string { return fmt.Sprintf(rolloutwords.EventFallbackServed, failed, good) } // textConfigPending's reason is a reason word. func textConfigPending(reason string, wait time.Duration) string { return fmt.Sprintf(rolloutwords.EventConfigPending, rolloutwords.Reason(reason), wait.String()) } func textStagingRefused(build string) string { return fmt.Sprintf(rolloutwords.EventStagingRefused, build) }