|
| 1 | +package controlledsession |
| 2 | + |
| 3 | +import "fmt" |
| 4 | + |
| 5 | +type TerminationCauseV1 string |
| 6 | + |
| 7 | +const ( |
| 8 | + CauseControllerCompleteV1 TerminationCauseV1 = "controller-complete" |
| 9 | + CauseControllerTerminateV1 TerminationCauseV1 = "controller-terminate" |
| 10 | + CauseApplicationExitV1 TerminationCauseV1 = "application-exit" |
| 11 | + CauseHostCancelV1 TerminationCauseV1 = "host-cancel" |
| 12 | + CauseControllerLostV1 TerminationCauseV1 = "controller-lost" |
| 13 | + CauseRuntimeObservationLostV1 TerminationCauseV1 = "runtime-observation-lost" |
| 14 | + CauseStartupFailureV1 TerminationCauseV1 = "startup-failure" |
| 15 | +) |
| 16 | + |
| 17 | +type ProcessStatusKindV1 string |
| 18 | + |
| 19 | +const ( |
| 20 | + ProcessStatusUnknownV1 ProcessStatusKindV1 = "unknown" |
| 21 | + ProcessStatusExitedV1 ProcessStatusKindV1 = "exited" |
| 22 | + ProcessStatusTerminatedV1 ProcessStatusKindV1 = "terminated" |
| 23 | + ProcessStatusUnavailableV1 ProcessStatusKindV1 = "unavailable" |
| 24 | +) |
| 25 | + |
| 26 | +type ProcessStatusV1 struct { |
| 27 | + Kind ProcessStatusKindV1 `json:"kind"` |
| 28 | + Code *int `json:"code,omitempty"` |
| 29 | + Reason string `json:"reason,omitempty"` |
| 30 | +} |
| 31 | + |
| 32 | +type ControllerStatusKindV1 string |
| 33 | + |
| 34 | +const ( |
| 35 | + ControllerStatusUnknownV1 ControllerStatusKindV1 = "unknown" |
| 36 | + ControllerStatusActiveV1 ControllerStatusKindV1 = "active" |
| 37 | + ControllerStatusCompletedV1 ControllerStatusKindV1 = "completed" |
| 38 | + ControllerStatusLostV1 ControllerStatusKindV1 = "lost" |
| 39 | + ControllerStatusFinalizationTimeoutV1 ControllerStatusKindV1 = "finalization-timeout" |
| 40 | + ControllerStatusStoppedV1 ControllerStatusKindV1 = "stopped" |
| 41 | + ControllerStatusStartupFailedV1 ControllerStatusKindV1 = "startup-failed" |
| 42 | +) |
| 43 | + |
| 44 | +type ControllerStatusV1 struct { |
| 45 | + Kind ControllerStatusKindV1 `json:"kind"` |
| 46 | + Reason string `json:"reason,omitempty"` |
| 47 | +} |
| 48 | + |
| 49 | +type CleanupStatusKindV1 string |
| 50 | + |
| 51 | +const ( |
| 52 | + CleanupStatusSucceededV1 CleanupStatusKindV1 = "succeeded" |
| 53 | + CleanupStatusFailedV1 CleanupStatusKindV1 = "failed" |
| 54 | +) |
| 55 | + |
| 56 | +type CleanupStatusV1 struct { |
| 57 | + Kind CleanupStatusKindV1 `json:"kind"` |
| 58 | + Message string `json:"message,omitempty"` |
| 59 | +} |
| 60 | + |
| 61 | +type RecoveryActionV1 string |
| 62 | + |
| 63 | +const ( |
| 64 | + RecoveryNoneV1 RecoveryActionV1 = "none" |
| 65 | + RecoveryRetryCleanupV1 RecoveryActionV1 = "retry-cleanup" |
| 66 | + RecoveryReconcileNextOperationV1 RecoveryActionV1 = "reconcile-next-operation" |
| 67 | +) |
| 68 | + |
| 69 | +type ResultV1 struct { |
| 70 | + Cause TerminationCauseV1 `json:"cause"` |
| 71 | + ApplicationStatus ProcessStatusV1 `json:"application_status"` |
| 72 | + ControllerStatus ControllerStatusV1 `json:"controller_status"` |
| 73 | + CleanupStatus CleanupStatusV1 `json:"cleanup_status"` |
| 74 | + RecoveryAction RecoveryActionV1 `json:"recovery_action"` |
| 75 | +} |
| 76 | + |
| 77 | +func ValidateResultV1(result ResultV1) error { |
| 78 | + if !validTerminationCauseV1(result.Cause) { |
| 79 | + return fmt.Errorf("controlled-session termination cause %q is invalid", result.Cause) |
| 80 | + } |
| 81 | + if err := validateProcessStatusV1(result.ApplicationStatus, true); err != nil { |
| 82 | + return err |
| 83 | + } |
| 84 | + if err := validateTerminalControllerStatusV1(result.ControllerStatus); err != nil { |
| 85 | + return err |
| 86 | + } |
| 87 | + return validateCleanupResultV1(result.CleanupStatus, result.RecoveryAction) |
| 88 | +} |
| 89 | + |
| 90 | +func validateProcessStatusV1(status ProcessStatusV1, allowUnknown bool) error { |
| 91 | + switch status.Kind { |
| 92 | + case ProcessStatusUnknownV1: |
| 93 | + if !allowUnknown || status.Code != nil || status.Reason != "" { |
| 94 | + return fmt.Errorf("unknown application status must have no code or reason") |
| 95 | + } |
| 96 | + case ProcessStatusExitedV1: |
| 97 | + if status.Code == nil { |
| 98 | + return fmt.Errorf("exited application status requires an exit code") |
| 99 | + } |
| 100 | + case ProcessStatusTerminatedV1, ProcessStatusUnavailableV1: |
| 101 | + if status.Code != nil { |
| 102 | + return fmt.Errorf("application status %q must not contain an exit code", status.Kind) |
| 103 | + } |
| 104 | + default: |
| 105 | + return fmt.Errorf("application status %q is invalid", status.Kind) |
| 106 | + } |
| 107 | + return validateOptionalSafeTextV1("application status reason", status.Reason) |
| 108 | +} |
| 109 | + |
| 110 | +func validateTerminalControllerStatusV1(status ControllerStatusV1) error { |
| 111 | + switch status.Kind { |
| 112 | + case ControllerStatusCompletedV1, ControllerStatusLostV1, ControllerStatusFinalizationTimeoutV1, ControllerStatusStoppedV1, ControllerStatusStartupFailedV1: |
| 113 | + default: |
| 114 | + return fmt.Errorf("terminal controller status %q is invalid", status.Kind) |
| 115 | + } |
| 116 | + return validateOptionalSafeTextV1("controller status reason", status.Reason) |
| 117 | +} |
| 118 | + |
| 119 | +func validateCleanupResultV1(status CleanupStatusV1, recovery RecoveryActionV1) error { |
| 120 | + switch status.Kind { |
| 121 | + case CleanupStatusSucceededV1: |
| 122 | + if status.Message != "" { |
| 123 | + return fmt.Errorf("successful cleanup status must not contain an error message") |
| 124 | + } |
| 125 | + if recovery != RecoveryNoneV1 { |
| 126 | + return fmt.Errorf("successful cleanup must not require recovery") |
| 127 | + } |
| 128 | + case CleanupStatusFailedV1: |
| 129 | + if err := validateRequiredSafeTextV1("cleanup failure message", status.Message); err != nil { |
| 130 | + return err |
| 131 | + } |
| 132 | + if recovery != RecoveryRetryCleanupV1 && recovery != RecoveryReconcileNextOperationV1 { |
| 133 | + return fmt.Errorf("failed cleanup requires a recovery action") |
| 134 | + } |
| 135 | + default: |
| 136 | + return fmt.Errorf("cleanup status %q is invalid", status.Kind) |
| 137 | + } |
| 138 | + return nil |
| 139 | +} |
| 140 | + |
| 141 | +func validateOptionalSafeTextV1(field string, value string) error { |
| 142 | + if value == "" { |
| 143 | + return nil |
| 144 | + } |
| 145 | + return validateSafeTextV1(field, value) |
| 146 | +} |
| 147 | + |
| 148 | +func validateRequiredSafeTextV1(field string, value string) error { |
| 149 | + if value == "" { |
| 150 | + return fmt.Errorf("controlled-session %s is required", field) |
| 151 | + } |
| 152 | + return validateSafeTextV1(field, value) |
| 153 | +} |
| 154 | + |
| 155 | +func validTerminationCauseV1(cause TerminationCauseV1) bool { |
| 156 | + switch cause { |
| 157 | + case CauseControllerCompleteV1, CauseControllerTerminateV1, CauseApplicationExitV1, CauseHostCancelV1, |
| 158 | + CauseControllerLostV1, CauseRuntimeObservationLostV1, CauseStartupFailureV1: |
| 159 | + return true |
| 160 | + default: |
| 161 | + return false |
| 162 | + } |
| 163 | +} |
0 commit comments