feat: runtime manager

This commit is contained in:
Ilia Denisov
2026-04-28 20:39:18 +02:00
committed by GitHub
parent e0a99b346b
commit a7cee15115
289 changed files with 45660 additions and 2207 deletions
+10
View File
@@ -363,3 +363,13 @@ The implementation is complete only when all of the following hold:
notification types
- admin notifications remain `email`-only
- auth-code email still bypasses `Notification Service`
## Note: Runtime Manager Catalog Extension
The three administrator-only `runtime.*` notification types
(`runtime.image_pull_failed`, `runtime.container_start_failed`,
`runtime.start_config_invalid`) are added by the Runtime Manager
implementation plan, not by this document. See
[`../rtmanager/PLAN.md`](../rtmanager/PLAN.md) §«Stage 07. Notification
intent constructors and catalog extension». No new stages are added here
for that catalog growth.
+16 -1
View File
@@ -208,6 +208,9 @@ Primary configuration groups:
- `NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED`
- `NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START`
- `NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED`
- `NOTIFICATION_ADMIN_EMAILS_RUNTIME_IMAGE_PULL_FAILED`
- `NOTIFICATION_ADMIN_EMAILS_RUNTIME_CONTAINER_START_FAILED`
- `NOTIFICATION_ADMIN_EMAILS_RUNTIME_START_CONFIG_INVALID`
- OpenTelemetry:
- standard `OTEL_*` variables
- `NOTIFICATION_OTEL_STDOUT_TRACES_ENABLED`
@@ -292,10 +295,13 @@ Accepted intents use the original Redis Stream `stream_entry_id` as
| `lobby.race_name.registration_eligible` | `Game Lobby` (`game_lobby`) | capable member (`audience_kind=user`) | `push+email` | `game_id`, `game_name`, `race_name`, `eligible_until_ms` |
| `lobby.race_name.registered` | `Game Lobby` (`game_lobby`) | registering user (`audience_kind=user`) | `push+email` | `race_name` |
| `lobby.race_name.registration_denied` | `Game Lobby` (`game_lobby`) | incapable member (`audience_kind=user`) | `email` | `game_id`, `game_name`, `race_name`, `reason` |
| `runtime.image_pull_failed` | `Runtime Manager` (`runtime_manager`) | configured admin email list (`audience_kind=admin_email`) | `email` | `game_id`, `image_ref`, `error_code`, `error_message`, `attempted_at_ms` |
| `runtime.container_start_failed` | `Runtime Manager` (`runtime_manager`) | configured admin email list (`audience_kind=admin_email`) | `email` | `game_id`, `image_ref`, `error_code`, `error_message`, `attempted_at_ms` |
| `runtime.start_config_invalid` | `Runtime Manager` (`runtime_manager`) | configured admin email list (`audience_kind=admin_email`) | `email` | `game_id`, `image_ref`, `error_code`, `error_message`, `attempted_at_ms` |
Rules:
- v1 supports exactly the fifteen `notification_type` values listed above
- v1 supports exactly the eighteen `notification_type` values listed above
- `lobby.application.submitted` keeps one stable `notification_type` and one
stable `payload_json` shape; private games publish `audience_kind=user`
while public games publish `audience_kind=admin_email`
@@ -308,6 +314,12 @@ Rules:
with a 30-day `eligible_until_ms` window
- `lobby.race_name.registered` is emitted on successful
`lobby.race_name.register` commit
- the three `runtime.*` types are emitted by `Runtime Manager` only on
first-touch start failures (image pull, container create/start, start
configuration validation); they are administrator-only in v1 and have no
push counterpart. `Runtime Manager` does not publish notifications for
ongoing health changes — those flow through `runtime:health_events` and
are escalated by `Game Master` if needed.
## Recipient Enrichment And Locale Policy
@@ -436,6 +448,9 @@ Initial notification-owned template assets:
| `lobby.race_name.registration_eligible` | `lobby.race_name.registration_eligible` | `en/subject.tmpl`, `en/text.tmpl` |
| `lobby.race_name.registered` | `lobby.race_name.registered` | `en/subject.tmpl`, `en/text.tmpl` |
| `lobby.race_name.registration_denied` | `lobby.race_name.registration_denied` | `en/subject.tmpl`, `en/text.tmpl` |
| `runtime.image_pull_failed` | `runtime.image_pull_failed` | `en/subject.tmpl`, `en/text.tmpl` |
| `runtime.container_start_failed` | `runtime.container_start_failed` | `en/subject.tmpl`, `en/text.tmpl` |
| `runtime.start_config_invalid` | `runtime.start_config_invalid` | `en/subject.tmpl`, `en/text.tmpl` |
`auth.login_code` does not belong to the notification-owned template set.
+133
View File
@@ -58,6 +58,15 @@ components:
idempotency_key: game-lobby:game-456:application-submitted:user-42
occurred_at_ms: "1775121700002"
payload_json: '{"game_id":"game-456","game_name":"Orion Front","applicant_user_id":"user-42","applicant_name":"Nova Pilot"}'
- name: runtimeImagePullFailed
summary: Administrator email notification about a failed engine image pull.
payload:
notification_type: runtime.image_pull_failed
producer: runtime_manager
audience_kind: admin_email
idempotency_key: runtime-manager:game-789:image-pull-failed:1775121700003
occurred_at_ms: "1775121700003"
payload_json: '{"game_id":"game-789","image_ref":"galaxy/game:1.4.7","error_code":"image_pull_failed","error_message":"manifest unknown","attempted_at_ms":1775121700003}'
schemas:
NotificationIntentEnvelope:
type: object
@@ -98,6 +107,9 @@ components:
- lobby.race_name.registration_eligible
- lobby.race_name.registered
- lobby.race_name.registration_denied
- runtime.image_pull_failed
- runtime.container_start_failed
- runtime.start_config_invalid
description: |
Exact v1 notification type catalog. `lobby.invite.revoked`
deliberately remains outside the supported catalog because it
@@ -108,6 +120,7 @@ components:
- geoprofile
- game_master
- game_lobby
- runtime_manager
description: |
Stable producer identifier. The exact producer value is frozen per
`notification_type` by the v1 catalog.
@@ -419,6 +432,51 @@ components:
payload_json:
contentSchema:
$ref: '#/components/schemas/LobbyRaceNameRegistrationDeniedPayload'
- if:
properties:
notification_type:
const: runtime.image_pull_failed
required:
- notification_type
then:
properties:
producer:
const: runtime_manager
audience_kind:
const: admin_email
payload_json:
contentSchema:
$ref: '#/components/schemas/RuntimeImagePullFailedPayload'
- if:
properties:
notification_type:
const: runtime.container_start_failed
required:
- notification_type
then:
properties:
producer:
const: runtime_manager
audience_kind:
const: admin_email
payload_json:
contentSchema:
$ref: '#/components/schemas/RuntimeContainerStartFailedPayload'
- if:
properties:
notification_type:
const: runtime.start_config_invalid
required:
- notification_type
then:
properties:
producer:
const: runtime_manager
audience_kind:
const: admin_email
payload_json:
contentSchema:
$ref: '#/components/schemas/RuntimeStartConfigInvalidPayload'
GeoReviewRecommendedPayload:
type: object
additionalProperties: true
@@ -697,3 +755,78 @@ components:
reason:
type: string
minLength: 1
RuntimeImagePullFailedPayload:
type: object
additionalProperties: true
required:
- game_id
- image_ref
- error_code
- error_message
- attempted_at_ms
properties:
game_id:
type: string
minLength: 1
image_ref:
type: string
minLength: 1
error_code:
type: string
minLength: 1
error_message:
type: string
minLength: 1
attempted_at_ms:
type: integer
minimum: 1
RuntimeContainerStartFailedPayload:
type: object
additionalProperties: true
required:
- game_id
- image_ref
- error_code
- error_message
- attempted_at_ms
properties:
game_id:
type: string
minLength: 1
image_ref:
type: string
minLength: 1
error_code:
type: string
minLength: 1
error_message:
type: string
minLength: 1
attempted_at_ms:
type: integer
minimum: 1
RuntimeStartConfigInvalidPayload:
type: object
additionalProperties: true
required:
- game_id
- image_ref
- error_code
- error_message
- attempted_at_ms
properties:
game_id:
type: string
minLength: 1
image_ref:
type: string
minLength: 1
error_code:
type: string
minLength: 1
error_message:
type: string
minLength: 1
attempted_at_ms:
type: integer
minimum: 1
+26 -2
View File
@@ -35,6 +35,9 @@ var expectedNotificationTypeCatalog = []string{
"lobby.race_name.registration_eligible",
"lobby.race_name.registered",
"lobby.race_name.registration_denied",
"runtime.image_pull_failed",
"runtime.container_start_failed",
"runtime.start_config_invalid",
}
var expectedNotificationCatalog = map[string]notificationCatalogExpectation{
@@ -128,6 +131,24 @@ var expectedNotificationCatalog = map[string]notificationCatalogExpectation{
payloadSchema: "LobbyRaceNameRegistrationDeniedPayload",
requiredFields: []string{"game_id", "game_name", "race_name", "reason"},
},
"runtime.image_pull_failed": {
producer: "runtime_manager",
audienceKind: "admin_email",
payloadSchema: "RuntimeImagePullFailedPayload",
requiredFields: []string{"game_id", "image_ref", "error_code", "error_message", "attempted_at_ms"},
},
"runtime.container_start_failed": {
producer: "runtime_manager",
audienceKind: "admin_email",
payloadSchema: "RuntimeContainerStartFailedPayload",
requiredFields: []string{"game_id", "image_ref", "error_code", "error_message", "attempted_at_ms"},
},
"runtime.start_config_invalid": {
producer: "runtime_manager",
audienceKind: "admin_email",
payloadSchema: "RuntimeStartConfigInvalidPayload",
requiredFields: []string{"game_id", "image_ref", "error_code", "error_message", "attempted_at_ms"},
},
}
const expectedNotificationCatalogTable = `| ` + "`notification_type`" + ` | Producer | Audience | Channels | Required ` + "`payload_json`" + ` fields |
@@ -146,7 +167,10 @@ const expectedNotificationCatalogTable = `| ` + "`notification_type`" + ` | Prod
| ` + "`lobby.invite.expired`" + ` | ` + "`Game Lobby`" + ` (` + "`game_lobby`" + `) | private-game owner (` + "`audience_kind=user`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`game_name`" + `, ` + "`invitee_user_id`" + `, ` + "`invitee_name`" + ` |
| ` + "`lobby.race_name.registration_eligible`" + ` | ` + "`Game Lobby`" + ` (` + "`game_lobby`" + `) | capable member (` + "`audience_kind=user`" + `) | ` + "`push+email`" + ` | ` + "`game_id`" + `, ` + "`game_name`" + `, ` + "`race_name`" + `, ` + "`eligible_until_ms`" + ` |
| ` + "`lobby.race_name.registered`" + ` | ` + "`Game Lobby`" + ` (` + "`game_lobby`" + `) | registering user (` + "`audience_kind=user`" + `) | ` + "`push+email`" + ` | ` + "`race_name`" + ` |
| ` + "`lobby.race_name.registration_denied`" + ` | ` + "`Game Lobby`" + ` (` + "`game_lobby`" + `) | incapable member (` + "`audience_kind=user`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`game_name`" + `, ` + "`race_name`" + `, ` + "`reason`" + ` |`
| ` + "`lobby.race_name.registration_denied`" + ` | ` + "`Game Lobby`" + ` (` + "`game_lobby`" + `) | incapable member (` + "`audience_kind=user`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`game_name`" + `, ` + "`race_name`" + `, ` + "`reason`" + ` |
| ` + "`runtime.image_pull_failed`" + ` | ` + "`Runtime Manager`" + ` (` + "`runtime_manager`" + `) | configured admin email list (` + "`audience_kind=admin_email`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`image_ref`" + `, ` + "`error_code`" + `, ` + "`error_message`" + `, ` + "`attempted_at_ms`" + ` |
| ` + "`runtime.container_start_failed`" + ` | ` + "`Runtime Manager`" + ` (` + "`runtime_manager`" + `) | configured admin email list (` + "`audience_kind=admin_email`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`image_ref`" + `, ` + "`error_code`" + `, ` + "`error_message`" + `, ` + "`attempted_at_ms`" + ` |
| ` + "`runtime.start_config_invalid`" + ` | ` + "`Runtime Manager`" + ` (` + "`runtime_manager`" + `) | configured admin email list (` + "`audience_kind=admin_email`" + `) | ` + "`email`" + ` | ` + "`game_id`" + `, ` + "`image_ref`" + `, ` + "`error_code`" + `, ` + "`error_message`" + `, ` + "`attempted_at_ms`" + ` |`
var expectedSharedDocumentationSnippets = []string{
"`lobby.application.submitted` keeps one stable `notification_type` and one stable `payload_json` shape",
@@ -234,7 +258,7 @@ func TestIntentAsyncAPISpecFreezesEnvelopeSchema(t *testing.T) {
producer := getMapValue(t, properties, "producer")
require.Equal(t, "string", getStringValue(t, producer, "type"))
require.Equal(t, []string{"geoprofile", "game_master", "game_lobby"}, getStringSlice(t, producer, "enum"))
require.Equal(t, []string{"geoprofile", "game_master", "game_lobby", "runtime_manager"}, getStringSlice(t, producer, "enum"))
occurredAt := getMapValue(t, properties, "occurred_at_ms")
require.Equal(t, "string", getStringValue(t, occurredAt, "type"))
@@ -85,6 +85,18 @@ const (
// NotificationTypeLobbyRaceNameRegistrationDenied identifies the
// `lobby.race_name.registration_denied` notification.
NotificationTypeLobbyRaceNameRegistrationDenied = notificationintent.NotificationTypeLobbyRaceNameRegistrationDenied
// NotificationTypeRuntimeImagePullFailed identifies the
// `runtime.image_pull_failed` notification.
NotificationTypeRuntimeImagePullFailed = notificationintent.NotificationTypeRuntimeImagePullFailed
// NotificationTypeRuntimeContainerStartFailed identifies the
// `runtime.container_start_failed` notification.
NotificationTypeRuntimeContainerStartFailed = notificationintent.NotificationTypeRuntimeContainerStartFailed
// NotificationTypeRuntimeStartConfigInvalid identifies the
// `runtime.start_config_invalid` notification.
NotificationTypeRuntimeStartConfigInvalid = notificationintent.NotificationTypeRuntimeStartConfigInvalid
)
// Producer identifies one supported upstream producer.
@@ -99,6 +111,9 @@ const (
// ProducerGameLobby identifies Game Lobby.
ProducerGameLobby = notificationintent.ProducerGameLobby
// ProducerRuntimeManager identifies Runtime Manager.
ProducerRuntimeManager = notificationintent.ProducerRuntimeManager
)
// AudienceKind identifies one supported target-audience kind.
+38 -14
View File
@@ -46,10 +46,13 @@ const (
userServiceBaseURLEnvVar = "NOTIFICATION_USER_SERVICE_BASE_URL"
userServiceTimeoutEnvVar = "NOTIFICATION_USER_SERVICE_TIMEOUT"
adminEmailsGeoReviewRecommendedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED"
adminEmailsGameGenerationFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED"
adminEmailsLobbyRuntimePausedAfterEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START"
adminEmailsLobbyApplicationSubmittedEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED"
adminEmailsGeoReviewRecommendedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED"
adminEmailsGameGenerationFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED"
adminEmailsLobbyRuntimePausedAfterEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START"
adminEmailsLobbyApplicationSubmittedEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED"
adminEmailsRuntimeImagePullFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_IMAGE_PULL_FAILED"
adminEmailsRuntimeContainerStartFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_CONTAINER_START_FAILED"
adminEmailsRuntimeStartConfigInvalidEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_START_CONFIG_INVALID"
otelServiceNameEnvVar = "OTEL_SERVICE_NAME"
otelTracesExporterEnvVar = "OTEL_TRACES_EXPORTER"
@@ -60,18 +63,18 @@ const (
otelStdoutTracesEnabledEnvVar = "NOTIFICATION_OTEL_STDOUT_TRACES_ENABLED"
otelStdoutMetricsEnabledEnvVar = "NOTIFICATION_OTEL_STDOUT_METRICS_ENABLED"
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8092"
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8092"
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultIntentsStream = "notification:intents"
defaultIntentsReadBlockTimeout = 2 * time.Second
defaultGatewayClientEventsStream = "gateway:client-events"
defaultIntentsStream = "notification:intents"
defaultIntentsReadBlockTimeout = 2 * time.Second
defaultGatewayClientEventsStream = "gateway:client-events"
defaultGatewayClientEventsStreamMaxLen int64 = 1024
defaultMailDeliveryCommandsStream = "mail:delivery_commands"
defaultMailDeliveryCommandsStream = "mail:delivery_commands"
defaultPushRetryMaxAttempts = 3
defaultEmailRetryMaxAttempts = 7
@@ -352,6 +355,18 @@ type AdminRoutingConfig struct {
// LobbyApplicationSubmitted stores recipients for public
// `lobby.application.submitted` notifications.
LobbyApplicationSubmitted []string
// RuntimeImagePullFailed stores recipients for
// `runtime.image_pull_failed`.
RuntimeImagePullFailed []string
// RuntimeContainerStartFailed stores recipients for
// `runtime.container_start_failed`.
RuntimeContainerStartFailed []string
// RuntimeStartConfigInvalid stores recipients for
// `runtime.start_config_invalid`.
RuntimeStartConfigInvalid []string
}
// Validate reports whether cfg stores valid normalized administrator email
@@ -369,6 +384,15 @@ func (cfg AdminRoutingConfig) Validate() error {
if err := validateNormalizedEmailList("lobby.application.submitted", cfg.LobbyApplicationSubmitted); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.image_pull_failed", cfg.RuntimeImagePullFailed); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.container_start_failed", cfg.RuntimeContainerStartFailed); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.start_config_invalid", cfg.RuntimeStartConfigInvalid); err != nil {
return err
}
return nil
}
+12 -5
View File
@@ -19,11 +19,11 @@ const (
envRedisTLSEnabled = "NOTIFICATION_REDIS_TLS_ENABLED"
envRedisUsername = "NOTIFICATION_REDIS_USERNAME"
envPostgresPrimaryDSN = "NOTIFICATION_POSTGRES_PRIMARY_DSN"
envPostgresOpTimeout = "NOTIFICATION_POSTGRES_OPERATION_TIMEOUT"
envPostgresMaxOpenConns = "NOTIFICATION_POSTGRES_MAX_OPEN_CONNS"
envPostgresMaxIdleConns = "NOTIFICATION_POSTGRES_MAX_IDLE_CONNS"
envPostgresConnMaxLife = "NOTIFICATION_POSTGRES_CONN_MAX_LIFETIME"
envPostgresPrimaryDSN = "NOTIFICATION_POSTGRES_PRIMARY_DSN"
envPostgresOpTimeout = "NOTIFICATION_POSTGRES_OPERATION_TIMEOUT"
envPostgresMaxOpenConns = "NOTIFICATION_POSTGRES_MAX_OPEN_CONNS"
envPostgresMaxIdleConns = "NOTIFICATION_POSTGRES_MAX_IDLE_CONNS"
envPostgresConnMaxLife = "NOTIFICATION_POSTGRES_CONN_MAX_LIFETIME"
)
const (
@@ -104,6 +104,9 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
t.Setenv(adminEmailsGameGenerationFailedEnvVar, "ops@example.com")
t.Setenv(adminEmailsLobbyRuntimePausedAfterEnvVar, "pause@example.com, PAUSE@example.com")
t.Setenv(adminEmailsLobbyApplicationSubmittedEnvVar, "owner@example.com, OWNER@example.com")
t.Setenv(adminEmailsRuntimeImagePullFailedEnvVar, "image-pull-ops@example.com, IMAGE-PULL-OPS@example.com")
t.Setenv(adminEmailsRuntimeContainerStartFailedEnvVar, "container-start-ops@example.com")
t.Setenv(adminEmailsRuntimeStartConfigInvalidEnvVar, "start-config-ops@example.com, START-CONFIG-OPS@example.com")
t.Setenv(otelServiceNameEnvVar, "custom-notification")
t.Setenv(otelTracesExporterEnvVar, "otlp")
t.Setenv(otelMetricsExporterEnvVar, "otlp")
@@ -172,6 +175,9 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
GameGenerationFailed: []string{"ops@example.com"},
LobbyRuntimePausedAfterStart: []string{"pause@example.com"},
LobbyApplicationSubmitted: []string{"owner@example.com"},
RuntimeImagePullFailed: []string{"image-pull-ops@example.com"},
RuntimeContainerStartFailed: []string{"container-start-ops@example.com"},
RuntimeStartConfigInvalid: []string{"start-config-ops@example.com"},
}, cfg.AdminRouting)
require.Equal(t, TelemetryConfig{
ServiceName: "custom-notification",
@@ -295,6 +301,7 @@ func TestLoadFromEnvRejectsInvalidConfiguration(t *testing.T) {
{name: "invalid admin email", envName: adminEmailsGeoReviewRecommendedEnvVar, envVal: "broken-email", want: "invalid email address"},
{name: "blank admin email slot", envName: adminEmailsGameGenerationFailedEnvVar, envVal: "ops@example.com, , second@example.com", want: "must not be empty"},
{name: "invalid public application admin email", envName: adminEmailsLobbyApplicationSubmittedEnvVar, envVal: "Owner <owner@example.com>", want: "must not include a display name"},
{name: "invalid runtime image pull admin email", envName: adminEmailsRuntimeImagePullFailedEnvVar, envVal: "broken-runtime-email", want: "invalid email address"},
{name: "nonpositive gateway client events stream max len", envName: gatewayClientEventsStreamMaxEnvVar, envVal: "0", want: "must be positive"},
{name: "backoff min above max", envName: routeBackoffMinEnvVar, envVal: "10m", want: "must not exceed"},
}
+12
View File
@@ -128,6 +128,18 @@ func LoadFromEnv() (Config, error) {
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeImagePullFailed, err = emailListEnv(adminEmailsRuntimeImagePullFailedEnvVar, cfg.AdminRouting.RuntimeImagePullFailed)
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeContainerStartFailed, err = emailListEnv(adminEmailsRuntimeContainerStartFailedEnvVar, cfg.AdminRouting.RuntimeContainerStartFailed)
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeStartConfigInvalid, err = emailListEnv(adminEmailsRuntimeStartConfigInvalidEnvVar, cfg.AdminRouting.RuntimeStartConfigInvalid)
if err != nil {
return Config{}, err
}
cfg.Telemetry.ServiceName = stringEnv(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
cfg.Telemetry.TracesExporter = normalizeExporterValue(stringEnv(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
@@ -809,6 +809,12 @@ func (service *Service) adminEmailsFor(notificationType intentstream.Notificatio
return append([]string(nil), service.adminRouting.LobbyRuntimePausedAfterStart...)
case intentstream.NotificationTypeLobbyApplicationSubmitted:
return append([]string(nil), service.adminRouting.LobbyApplicationSubmitted...)
case intentstream.NotificationTypeRuntimeImagePullFailed:
return append([]string(nil), service.adminRouting.RuntimeImagePullFailed...)
case intentstream.NotificationTypeRuntimeContainerStartFailed:
return append([]string(nil), service.adminRouting.RuntimeContainerStartFailed...)
case intentstream.NotificationTypeRuntimeStartConfigInvalid:
return append([]string(nil), service.adminRouting.RuntimeStartConfigInvalid...)
default:
return nil
}
+4 -1
View File
@@ -26,7 +26,10 @@ const expectedNotificationMailTemplateTable = `| ` + "`notification_type`" + ` |
| ` + "`lobby.invite.expired`" + ` | ` + "`lobby.invite.expired`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`lobby.race_name.registration_eligible`" + ` | ` + "`lobby.race_name.registration_eligible`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`lobby.race_name.registered`" + ` | ` + "`lobby.race_name.registered`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`lobby.race_name.registration_denied`" + ` | ` + "`lobby.race_name.registration_denied`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |`
| ` + "`lobby.race_name.registration_denied`" + ` | ` + "`lobby.race_name.registration_denied`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`runtime.image_pull_failed`" + ` | ` + "`runtime.image_pull_failed`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`runtime.container_start_failed`" + ` | ` + "`runtime.container_start_failed`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |
| ` + "`runtime.start_config_invalid`" + ` | ` + "`runtime.start_config_invalid`" + ` | ` + "`en/subject.tmpl`" + `, ` + "`en/text.tmpl`" + ` |`
var expectedNotificationMailReadmeSnippets = []string{
"`payload_mode` is always `template`",