1use std::{collections::BTreeMap, fmt, str::FromStr};
11
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15pub const MIN_SUPPORTED_POSTGRES_MAJOR: u16 = 18;
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18#[serde(try_from = "String", into = "String")]
19pub struct PostgresVersion {
20 major: u16,
21 original: String,
22}
23
24impl PostgresVersion {
25 pub fn new(value: impl Into<String>) -> Result<Self, ModelError> {
26 let original = value.into();
27 let major = parse_major(&original)?;
28 if major < MIN_SUPPORTED_POSTGRES_MAJOR {
29 return Err(ModelError::UnsupportedPostgresVersion {
30 version: original,
31 minimum_major: MIN_SUPPORTED_POSTGRES_MAJOR,
32 });
33 }
34 Ok(Self { major, original })
35 }
36
37 pub const fn major(&self) -> u16 {
38 self.major
39 }
40
41 pub fn as_str(&self) -> &str {
42 &self.original
43 }
44}
45
46impl fmt::Display for PostgresVersion {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(&self.original)
49 }
50}
51
52impl From<PostgresVersion> for String {
53 fn from(value: PostgresVersion) -> Self {
54 value.original
55 }
56}
57
58impl FromStr for PostgresVersion {
59 type Err = ModelError;
60
61 fn from_str(value: &str) -> Result<Self, Self::Err> {
62 Self::new(value)
63 }
64}
65
66impl TryFrom<String> for PostgresVersion {
67 type Error = ModelError;
68
69 fn try_from(value: String) -> Result<Self, Self::Error> {
70 Self::new(value)
71 }
72}
73
74fn parse_major(value: &str) -> Result<u16, ModelError> {
75 let trimmed = value.trim();
76 if trimmed.is_empty() {
77 return Err(ModelError::InvalidPostgresVersion(value.to_owned()));
78 }
79 let major = trimmed
80 .split(['.', '-'])
81 .next()
82 .ok_or_else(|| ModelError::InvalidPostgresVersion(value.to_owned()))?;
83 major
84 .parse()
85 .map_err(|_| ModelError::InvalidPostgresVersion(value.to_owned()))
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum DatabaseMode {
91 Managed,
92 Local,
93 External,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub struct EnvironmentSpec {
98 pub organization_id: String,
99 pub project_id: String,
100 pub environment_id: String,
101 pub region: String,
102 pub database: DatabaseSpec,
103 pub sync: SyncDeploymentSpec,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107pub struct Organization {
108 pub organization_id: String,
109 pub name: String,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct Project {
114 pub project_id: String,
115 pub organization_id: String,
116 pub name: String,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct Environment {
121 pub environment_id: String,
122 pub organization_id: String,
123 pub project_id: String,
124 pub name: String,
125 pub region: String,
126}
127
128impl EnvironmentSpec {
129 pub fn validate(&self) -> Result<(), ModelError> {
130 self.database.validate()
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135pub struct DatabaseSpec {
136 pub mode: DatabaseMode,
137 pub postgres_version: Option<PostgresVersion>,
138 pub managed: Option<ManagedPostgresSpec>,
139}
140
141impl DatabaseSpec {
142 pub fn validate(&self) -> Result<(), ModelError> {
143 match self.mode {
144 DatabaseMode::Managed | DatabaseMode::Local => {
145 if self.postgres_version.is_none() {
146 return Err(ModelError::MissingPostgresVersion {
147 mode: self.mode.clone(),
148 });
149 }
150 }
151 DatabaseMode::External => {}
152 }
153
154 if matches!(self.mode, DatabaseMode::Managed) && self.managed.is_none() {
155 return Err(ModelError::MissingManagedPostgresSpec);
156 }
157
158 Ok(())
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct ManagedPostgresSpec {
164 pub cluster_id: String,
165 pub tier: String,
166 pub storage_gib: u32,
167 pub backup_policy: BackupPolicy,
168 pub maintenance_policy: MaintenancePolicy,
169 #[serde(default)]
170 pub roles: Vec<DatabaseRoleSpec>,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174pub struct BackupPolicy {
175 pub pitr_window_hours: u16,
176 pub base_backup_interval_hours: u16,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub struct MaintenancePolicy {
181 pub window: String,
182 pub auto_minor_upgrades: bool,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct SyncDeploymentSpec {
187 pub deployment_id: String,
188 pub config_version: String,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192pub struct ConfigVersion {
193 pub config_version: String,
194 pub environment_id: String,
195 pub rendered_hash: String,
196 pub status: ConfigVersionStatus,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
200pub struct SyncDeployment {
201 pub deployment_id: String,
202 pub organization_id: String,
203 pub project_id: String,
204 pub environment_id: String,
205 pub managed_postgres_cluster_id: String,
206 pub config_version: String,
207 pub lifecycle_state: SyncDeploymentLifecycleState,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(rename_all = "snake_case")]
212pub enum SyncDeploymentLifecycleState {
213 Requested,
214 Starting,
215 Running,
216 Draining,
217 Stopped,
218 Failed,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct GatewayRoute {
223 pub host: String,
224 pub organization_id: String,
225 pub project_id: String,
226 pub environment_id: String,
227 pub sync_endpoint: String,
228 pub tls_policy: TlsPolicy,
229 pub rate_limit: RateLimitPolicy,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct Domain {
234 pub domain_id: String,
235 pub hostname: String,
236 pub organization_id: String,
237 pub project_id: String,
238 pub environment_id: String,
239 pub route_host: String,
240 pub verification_status: DomainVerificationStatus,
241 pub verification_token: String,
242 pub tls_status: DomainTlsStatus,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
246pub struct IpAllowlistRule {
247 pub rule_id: String,
248 pub organization_id: String,
249 pub project_id: String,
250 pub environment_id: String,
251 pub name: String,
252 pub cidr: String,
253 pub purpose: IpAllowlistPurpose,
254 pub status: IpAllowlistStatus,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258pub struct StaticEgressIp {
259 pub egress_ip_id: String,
260 pub organization_id: String,
261 pub project_id: String,
262 pub environment_id: String,
263 pub region: String,
264 pub ip_address: String,
265 pub provider_ref: String,
266 pub status: StaticEgressIpStatus,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270pub struct MaintenanceWindow {
271 pub window_id: String,
272 pub organization_id: String,
273 pub project_id: String,
274 pub environment_id: String,
275 pub name: String,
276 pub day_of_week: MaintenanceDayOfWeek,
277 pub start_time: String,
278 pub duration_minutes: u32,
279 pub auto_minor_upgrades: bool,
280 pub status: MaintenanceWindowStatus,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284#[serde(rename_all = "snake_case")]
285pub enum TlsPolicy {
286 TerminateAtGateway,
287 MutualTlsToSync {
288 ca_secret_ref: String,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 client_certificate_secret_ref: Option<String>,
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 client_private_key_secret_ref: Option<String>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
294 server_name: Option<String>,
295 },
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299pub struct RateLimitPolicy {
300 pub max_connections: u32,
301 pub max_requests_per_minute: u32,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305#[serde(rename_all = "snake_case")]
306pub enum DomainVerificationStatus {
307 Pending,
308 Verified,
309 Failed,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "snake_case")]
314pub enum DomainTlsStatus {
315 Pending,
316 Active,
317 Failed,
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum IpAllowlistPurpose {
323 App,
324 Migration,
325 Support,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(rename_all = "snake_case")]
330pub enum IpAllowlistStatus {
331 Active,
332 Disabled,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
336#[serde(rename_all = "snake_case")]
337pub enum StaticEgressIpStatus {
338 Provisioning,
339 Active,
340 Retired,
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
344#[serde(rename_all = "snake_case")]
345pub enum MaintenanceDayOfWeek {
346 Monday,
347 Tuesday,
348 Wednesday,
349 Thursday,
350 Friday,
351 Saturday,
352 Sunday,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
356#[serde(rename_all = "snake_case")]
357pub enum MaintenanceWindowStatus {
358 Active,
359 Disabled,
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363pub struct DatabaseProxyRoute {
364 pub listen_addr: String,
365 pub upstream_addr: String,
366 pub organization_id: String,
367 pub project_id: String,
368 pub environment_id: String,
369 pub cluster_id: String,
370 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub policy: Option<DatabaseProxyPolicy>,
372 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub tls: Option<DatabaseProxyTlsConfig>,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
377pub struct DatabaseProxyPolicy {
378 #[serde(default)]
379 pub allowed_users: Vec<String>,
380 #[serde(default)]
381 pub allowed_databases: Vec<String>,
382 #[serde(default)]
383 pub forbidden_startup_parameters: Vec<String>,
384 #[serde(default)]
385 pub required_startup_parameters: BTreeMap<String, String>,
386 #[serde(default)]
387 pub forbidden_simple_query_verbs: Vec<String>,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub max_simple_query_bytes: Option<u32>,
390}
391
392#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub struct DatabaseProxyTlsConfig {
394 pub mode: DatabaseProxyTlsMode,
395 pub certificate_id: String,
396 pub common_name: String,
397 pub certificate_secret_ref: SecretRef,
398 pub private_key_secret_ref: SecretRef,
399 pub fingerprint_sha256: String,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(rename_all = "snake_case")]
404pub enum DatabaseProxyTlsMode {
405 TerminateAtProxy,
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
409#[serde(rename_all = "snake_case")]
410pub enum ConfigVersionStatus {
411 Uploaded,
412 Validated,
413 Deployed,
414 Failed,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418pub struct DatabaseRoleSpec {
419 pub name: String,
420 pub kind: DatabaseRoleKind,
421 pub secret_ref: Option<SecretRef>,
422 #[serde(default)]
423 pub privileges: Vec<String>,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427pub struct DatabaseRoleCredential {
428 pub name: String,
429 pub kind: DatabaseRoleKind,
430 pub password: String,
431 #[serde(default)]
432 pub privileges: Vec<String>,
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
436#[serde(rename_all = "snake_case")]
437pub enum DatabaseRoleKind {
438 App,
439 Migration,
440 Replication,
441 Support,
442}
443
444#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
445pub struct ManagedPostgresCluster {
446 pub cluster_id: String,
447 pub organization_id: String,
448 pub project_id: String,
449 pub environment_id: String,
450 pub region: String,
451 pub postgres_version: PostgresVersion,
452 pub tier: String,
453 pub storage_gib: u32,
454 pub lifecycle_state: ClusterLifecycleState,
455 pub host_assignment: Option<HostAssignment>,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
459pub struct ManagedPostgresEndpoint {
460 pub environment_id: String,
461 pub active_cluster_id: String,
462 pub updated_by_failover_id: Option<String>,
463 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub database_proxy_listen_addr: Option<String>,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub active_certificate_id: Option<String>,
467}
468
469#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
470pub struct ManagedPostgresEndpointCertificate {
471 pub certificate_id: String,
472 pub environment_id: String,
473 pub listen_addr: String,
474 pub common_name: String,
475 pub status: CertificateLifecycleState,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
477 pub certificate_secret_ref: Option<SecretRef>,
478 pub private_key_secret_ref: SecretRef,
479 pub not_before: String,
480 pub not_after: String,
481 pub fingerprint_sha256: String,
482 pub issued_by: String,
483 pub error_message: Option<String>,
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
487pub struct ManagedPostgresEndpointCertificateBundle {
488 pub certificate: ManagedPostgresEndpointCertificate,
489 pub certificate_pem: String,
490 pub private_key_pem: String,
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
494pub struct GatewayRouteMtlsBundle {
495 pub host: String,
496 pub environment_id: String,
497 pub ca_secret_ref: String,
498 pub ca_pem: String,
499 pub client_certificate_secret_ref: Option<String>,
500 pub client_certificate_pem: Option<String>,
501 pub client_private_key_secret_ref: Option<String>,
502 pub client_private_key_pem: Option<String>,
503 pub server_name: Option<String>,
504}
505
506#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
507pub struct ManagedPostgresCertificateAuthorityProvider {
508 pub ca_provider_id: String,
509 pub name: String,
510 pub kind: CertificateAuthorityProviderKind,
511 pub issuer_ref: String,
512 pub status: CertificateAuthorityProviderStatus,
513 pub default_for_managed_postgres: bool,
514 pub updated_at: String,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
518#[serde(rename_all = "snake_case")]
519pub enum CertificateAuthorityProviderKind {
520 LocalDev,
521 Acme,
522 ExternalPki,
523}
524
525#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
526#[serde(rename_all = "snake_case")]
527pub enum CertificateAuthorityProviderStatus {
528 Active,
529 Disabled,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
533#[serde(rename_all = "snake_case")]
534pub enum CertificateLifecycleState {
535 Provisioning,
536 Active,
537 Rotating,
538 Revoked,
539 Failed,
540}
541
542#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
543pub struct ManagedPostgresAcmeOrder {
544 pub order_id: String,
545 pub certificate_id: String,
546 pub environment_id: String,
547 pub ca_provider_id: String,
548 pub common_name: String,
549 pub challenge_type: AcmeChallengeType,
550 pub challenge_token: String,
551 pub key_authorization_secret_ref: SecretRef,
552 pub csr_secret_ref: SecretRef,
553 pub directory_url: String,
554 pub account_ref: String,
555 pub status: AcmeOrderStatus,
556 pub error_message: Option<String>,
557 pub created_at: String,
558 pub updated_at: String,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
562pub enum AcmeChallengeType {
563 #[serde(rename = "http_01")]
564 Http01,
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
568#[serde(rename_all = "snake_case")]
569pub enum AcmeOrderStatus {
570 PendingChallenge,
571 ReadyToFinalize,
572 Succeeded,
573 Failed,
574}
575
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
577pub struct ManagedPostgresBackup {
578 pub backup_id: String,
579 pub cluster_id: String,
580 pub status: BackupLifecycleState,
581 pub backup_dir: String,
582 pub error_message: Option<String>,
583}
584
585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
586pub struct ManagedPostgresBackupArtifact {
587 pub artifact_id: String,
588 pub backup_id: String,
589 pub cluster_id: String,
590 pub provider: String,
591 pub object_uri: String,
592 pub manifest_path: String,
593 pub manifest_sha256: Option<String>,
594 pub size_bytes: Option<u64>,
595 pub status: BackupArtifactStatus,
596 pub created_at: String,
597 pub updated_at: String,
598 pub error_message: Option<String>,
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
602#[serde(rename_all = "snake_case")]
603pub enum BackupArtifactStatus {
604 Pending,
605 Available,
606 Expired,
607 Deleted,
608 Failed,
609}
610
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
612pub struct ManagedPostgresBackupRetentionPolicy {
613 pub cluster_id: String,
614 pub retention_days: u32,
615 pub keep_min_successful_backups: u32,
616 pub enabled: bool,
617 pub updated_at: String,
618}
619
620#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
621pub struct ManagedPostgresCloneRedactionPolicy {
622 pub policy_id: String,
623 pub organization_id: String,
624 pub project_id: String,
625 pub environment_id: String,
626 pub name: String,
627 pub status: CloneRedactionPolicyStatus,
628 pub rules: Vec<CloneRedactionRule>,
629 pub created_at: String,
630 pub updated_at: String,
631}
632
633#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
634#[serde(rename_all = "snake_case")]
635pub enum CloneRedactionPolicyStatus {
636 Active,
637 Disabled,
638}
639
640#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
641pub struct CloneRedactionRule {
642 pub table_schema: String,
643 pub table_name: String,
644 pub column_name: String,
645 pub method: CloneRedactionMethod,
646 #[serde(skip_serializing_if = "Option::is_none")]
647 pub static_value: Option<String>,
648}
649
650#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
651#[serde(rename_all = "snake_case")]
652pub enum CloneRedactionMethod {
653 Null,
654 StaticValue,
655 HashSha256,
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
659pub struct ManagedPostgresSupportAccessSession {
660 pub session_id: String,
661 pub cluster_id: String,
662 pub organization_id: String,
663 pub project_id: String,
664 pub environment_id: String,
665 pub requested_by: String,
666 pub approved_by: Option<String>,
667 pub revoked_by: Option<String>,
668 pub reason: String,
669 pub ticket_ref: Option<String>,
670 pub status: SupportAccessStatus,
671 pub requested_at: String,
672 pub approved_at: Option<String>,
673 pub expires_at: String,
674 pub revoked_at: Option<String>,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
678#[serde(rename_all = "snake_case")]
679pub enum SupportAccessStatus {
680 Requested,
681 Active,
682 Revoked,
683 Expired,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
687pub struct ManagedPostgresDeletionTombstone {
688 pub cluster_id: String,
689 pub organization_id: String,
690 pub project_id: String,
691 pub environment_id: String,
692 pub region: String,
693 pub postgres_version: PostgresVersion,
694 pub tier: String,
695 pub retained_backup_id: Option<String>,
696 pub deleted_at: String,
697 pub retention_expires_at: Option<String>,
698 pub unrecoverable_at: Option<String>,
699 pub expired_at: Option<String>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
703pub struct ManagedPostgresRestore {
704 pub restore_id: String,
705 pub source_cluster_id: String,
706 pub target_cluster_id: String,
707 pub target_environment_id: String,
708 pub backup_id: String,
709 pub status: RestoreLifecycleState,
710 pub redaction_policy_id: Option<String>,
711 pub recovery_target_lsn: Option<String>,
712 pub error_message: Option<String>,
713}
714
715#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
716pub struct ManagedPostgresRestoreDrill {
717 pub drill_id: String,
718 pub source_cluster_id: String,
719 pub restore_id: String,
720 pub backup_id: String,
721 pub target_cluster_id: String,
722 pub status: RestoreLifecycleState,
723 pub error_message: Option<String>,
724}
725
726#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
727pub struct ManagedPostgresWalArchiveSegment {
728 pub cluster_id: String,
729 pub segment_name: String,
730 pub status: WalArchiveSegmentStatus,
731 pub archive_dir: String,
732 pub error_message: Option<String>,
733}
734
735#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
736pub struct ManagedPostgresPitrCheck {
737 pub check_id: String,
738 pub cluster_id: String,
739 pub backup_id: Option<String>,
740 pub status: PitrCheckStatus,
741 pub segment_count: u32,
742 pub first_segment: Option<String>,
743 pub latest_segment: Option<String>,
744 pub error_message: Option<String>,
745}
746
747#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
748pub struct ManagedPostgresFailover {
749 pub failover_id: String,
750 pub source_cluster_id: String,
751 pub target_cluster_id: String,
752 pub status: FailoverLifecycleState,
753 pub error_message: Option<String>,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
757pub struct ManagedPostgresStandby {
758 pub standby_id: String,
759 pub source_cluster_id: String,
760 pub target_cluster_id: String,
761 pub backup_id: String,
762 pub status: StandbyLifecycleState,
763 pub error_message: Option<String>,
764}
765
766#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
767pub struct ManagedPostgresStandbyCheck {
768 pub check_id: String,
769 pub standby_id: String,
770 pub source_cluster_id: String,
771 pub target_cluster_id: String,
772 pub slot_name: String,
773 pub max_lag_bytes: u64,
774 pub status: StandbyCheckStatus,
775 pub error_message: Option<String>,
776}
777
778#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct ManagedPostgresRuntimeCheck {
780 pub check_id: String,
781 pub cluster_id: String,
782 pub status: RuntimeCheckStatus,
783 pub connection_count: u32,
784 pub max_connections: u32,
785 pub replication_slot_lag_bytes: Option<u64>,
786 pub long_running_query_count: u32,
787 pub blocked_lock_count: u32,
788 pub oldest_transaction_age_seconds: Option<u64>,
789 pub autovacuum_running: bool,
790 pub checked_at: String,
791 pub error_message: Option<String>,
792}
793
794#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
795pub struct ManagedPostgresMajorUpgrade {
796 pub upgrade_id: String,
797 pub cluster_id: String,
798 pub source_postgres_version: PostgresVersion,
799 pub target_postgres_version: PostgresVersion,
800 pub strategy: ManagedPostgresMajorUpgradeStrategy,
801 pub status: ManagedPostgresMajorUpgradeStatus,
802 pub command_id: Option<String>,
803 pub operation_id: Option<String>,
804 pub error_message: Option<String>,
805 pub created_at: String,
806 pub completed_at: Option<String>,
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
810#[serde(rename_all = "snake_case")]
811pub enum ManagedPostgresMajorUpgradeStrategy {
812 LogicalReplicationCopy,
813 PgUpgradeCopy,
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
817#[serde(rename_all = "snake_case")]
818pub enum ManagedPostgresMajorUpgradeStatus {
819 Running,
820 Succeeded,
821 Failed,
822 Cancelled,
823}
824
825#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
826#[serde(rename_all = "snake_case")]
827pub enum BackupLifecycleState {
828 Requested,
829 Running,
830 Succeeded,
831 Failed,
832 Deleted,
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
836#[serde(rename_all = "snake_case")]
837pub enum RestoreLifecycleState {
838 Requested,
839 Running,
840 Succeeded,
841 Failed,
842}
843
844#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
845#[serde(rename_all = "snake_case")]
846pub enum WalArchiveSegmentStatus {
847 Running,
848 Succeeded,
849 Failed,
850}
851
852#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
853#[serde(rename_all = "snake_case")]
854pub enum PitrCheckStatus {
855 Succeeded,
856 Failed,
857}
858
859#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
860#[serde(rename_all = "snake_case")]
861pub enum FailoverLifecycleState {
862 Running,
863 Succeeded,
864 Failed,
865}
866
867#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
868#[serde(rename_all = "snake_case")]
869pub enum StandbyLifecycleState {
870 Running,
871 Succeeded,
872 Failed,
873}
874
875#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
876#[serde(rename_all = "snake_case")]
877pub enum StandbyCheckStatus {
878 Running,
879 Succeeded,
880 Failed,
881}
882
883#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
884#[serde(rename_all = "snake_case")]
885pub enum RuntimeCheckStatus {
886 Healthy,
887 Degraded,
888 Failed,
889}
890
891#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
892pub struct HostAssignment {
893 pub host_id: String,
894 pub data_dir: String,
895 pub port: u16,
896}
897
898#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
899#[serde(rename_all = "snake_case")]
900pub enum ClusterLifecycleState {
901 Requested,
902 Placing,
903 AllocatingStorage,
904 InitializingPostgres,
905 ConfiguringRoles,
906 ConfiguringReplication,
907 Restoring,
908 Resizing,
909 UpdatingPostgres,
910 Starting,
911 Verifying,
912 Ready,
913 Stopping,
914 Stopped,
915 Deleting,
916 Deleted,
917 Failed,
918}
919
920#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
921pub struct NodeAgentCommand {
922 pub command_id: String,
923 pub cluster_id: String,
924 pub action: NodeAgentAction,
925}
926
927#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
928pub struct QueuedNodeAgentCommand {
929 pub host_id: String,
930 pub operation_id: Option<String>,
931 pub command: NodeAgentCommand,
932 pub status: AgentCommandStatus,
933 pub attempts: u32,
934 pub last_error: Option<String>,
935 #[serde(skip_serializing_if = "Option::is_none")]
936 pub operation_token: Option<String>,
937}
938
939#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
940pub struct NodeAgentCommandResult {
941 pub command_id: String,
942 pub host_id: String,
943 pub status: AgentCommandStatus,
944 pub detail: Option<String>,
945 #[serde(skip_serializing_if = "Option::is_none")]
946 pub operation_token: Option<String>,
947 #[serde(default, skip_serializing_if = "Vec::is_empty")]
948 pub backup_artifacts: Vec<NodeAgentBackupArtifact>,
949}
950
951#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
952pub struct NodeAgentBackupArtifact {
953 pub backup_id: String,
954 pub provider: String,
955 pub object_uri: String,
956 pub manifest_path: String,
957 pub manifest_sha256: Option<String>,
958 pub size_bytes: Option<u64>,
959}
960
961#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
962#[serde(rename_all = "snake_case")]
963pub enum AgentCommandStatus {
964 Pending,
965 Running,
966 Succeeded,
967 Failed,
968 Cancelled,
969}
970
971#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
972#[serde(tag = "kind", rename_all = "snake_case")]
973pub enum NodeAgentAction {
974 PreparePostgres {
975 postgres_version: PostgresVersion,
976 data_dir: String,
977 port: u16,
978 },
979 StartPostgres,
980 StopPostgres,
981 DeletePostgresData {
982 data_dir: String,
983 tombstone_retention_days: Option<u32>,
984 },
985 ConfigurePostgresAccess {
986 data_dir: String,
987 port: u16,
988 database: String,
989 roles: Vec<DatabaseRoleCredential>,
990 publication: String,
991 replication_slot: String,
992 },
993 CreateCopyOnWriteDatabaseClone {
994 data_dir: String,
995 port: u16,
996 source_database: String,
997 target_database: String,
998 #[serde(default)]
999 terminate_source_connections: bool,
1000 },
1001 ReportStatus,
1002 CheckPostgresStandbyLag {
1003 source_data_dir: String,
1004 source_port: u16,
1005 database: String,
1006 slot_name: String,
1007 max_lag_bytes: u64,
1008 },
1009 RunBaseBackup {
1010 backup_id: String,
1011 data_dir: String,
1012 postgres_url: String,
1013 backup_dir: String,
1014 },
1015 DeleteBackupData {
1016 backup_id: String,
1017 backup_dir: String,
1018 },
1019 PrepareRestore {
1020 backup_id: String,
1021 backup_dir: String,
1022 data_dir: String,
1023 #[serde(default, skip_serializing_if = "Option::is_none")]
1024 target_port: Option<u16>,
1025 #[serde(default, skip_serializing_if = "Option::is_none")]
1026 database: Option<String>,
1027 restore_command: String,
1028 recovery_target_lsn: Option<String>,
1029 #[serde(skip_serializing_if = "Option::is_none")]
1030 redaction_policy: Option<ManagedPostgresCloneRedactionPolicy>,
1031 },
1032 PreparePostgresStandby {
1033 backup_id: String,
1034 backup_dir: String,
1035 data_dir: String,
1036 #[serde(default, skip_serializing_if = "Option::is_none")]
1037 target_port: Option<u16>,
1038 primary_conninfo: String,
1039 primary_slot_name: String,
1040 source_data_dir: String,
1041 source_port: u16,
1042 database: String,
1043 restore_command: String,
1044 },
1045 ArchiveWalSegment {
1046 source_path: String,
1047 archive_dir: String,
1048 segment_name: String,
1049 },
1050 PromotePostgresStandby {
1051 data_dir: String,
1052 },
1053 FencePostgresPrimary {
1054 data_dir: String,
1055 },
1056 ResizePostgresStorage {
1057 data_dir: String,
1058 storage_gib: u32,
1059 },
1060 UpdatePostgresMinor {
1061 data_dir: String,
1062 target_postgres_version: PostgresVersion,
1063 },
1064 UpgradePostgresMajor {
1065 data_dir: String,
1066 #[serde(default, skip_serializing_if = "Option::is_none")]
1067 source_port: Option<u16>,
1068 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 database: Option<String>,
1070 source_postgres_version: PostgresVersion,
1071 target_postgres_version: PostgresVersion,
1072 strategy: ManagedPostgresMajorUpgradeStrategy,
1073 },
1074 StartSyncDeployment {
1075 deployment_id: String,
1076 config_version: String,
1077 },
1078 StopSyncDeployment {
1079 deployment_id: String,
1080 },
1081 ReportSyncDeployment {
1082 deployment_id: String,
1083 },
1084}
1085
1086#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1087pub struct NodeAgentStatus {
1088 pub host_id: String,
1089 pub cluster_id: String,
1090 pub observed_state: ClusterLifecycleState,
1091 pub postgres_running: bool,
1092 pub postgres_version: Option<PostgresVersion>,
1093 pub backup_status: Option<BackupStatus>,
1094 pub wal_archive_status: Option<WalArchiveStatus>,
1095 pub detail: Option<String>,
1096}
1097
1098#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1099pub struct NodeHost {
1100 pub host_id: String,
1101 pub region: String,
1102 pub failure_domain: String,
1103 pub data_root: String,
1104 pub first_port: u16,
1105 pub state: NodeHostState,
1106 pub capacity: NodeHostCapacity,
1107}
1108
1109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1110pub struct NodeHostHardeningCheck {
1111 pub check_id: String,
1112 pub host_id: String,
1113 pub status: NodeHostHardeningStatus,
1114 pub image_ref: String,
1115 pub os_release: String,
1116 pub kernel_version: String,
1117 pub postgres_major_min: u16,
1118 pub container_runtime: String,
1119 pub disk_encryption: bool,
1120 pub firewall_enabled: bool,
1121 pub unattended_upgrades: bool,
1122 pub last_patched_at: Option<String>,
1123 pub checked_at: String,
1124 pub error_message: Option<String>,
1125}
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1128#[serde(rename_all = "snake_case")]
1129pub enum NodeHostHardeningStatus {
1130 Passing,
1131 Warning,
1132 Failing,
1133}
1134
1135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1136pub struct NodeHostAgentCredential {
1137 pub host_id: String,
1138 pub key_id: String,
1139 pub state: NodeHostAgentCredentialState,
1140 pub created_at: String,
1141 pub rotated_at: Option<String>,
1142 pub revoked_at: Option<String>,
1143 pub last_used_at: Option<String>,
1144 pub last_used_operation: Option<String>,
1145}
1146
1147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1148#[serde(rename_all = "snake_case")]
1149pub enum NodeHostAgentCredentialState {
1150 Active,
1151 Rotated,
1152 Revoked,
1153}
1154
1155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1156pub struct NodeHostHeartbeat {
1157 pub state: NodeHostState,
1158 pub capacity: NodeHostCapacity,
1159 #[serde(default)]
1160 pub observed_clusters: Vec<NodeHostClusterObservation>,
1161 #[serde(default)]
1162 pub observed_sync_deployments: Vec<NodeHostSyncDeploymentObservation>,
1163}
1164
1165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1166pub struct NodeHostClusterObservation {
1167 pub cluster_id: String,
1168 pub data_dir: String,
1169 pub postgres_running: bool,
1170}
1171
1172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1173pub struct NodeHostSyncDeploymentObservation {
1174 pub deployment_id: String,
1175 pub running: bool,
1176}
1177
1178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1179#[serde(rename_all = "snake_case")]
1180pub enum NodeHostState {
1181 Registering,
1182 Active,
1183 Draining,
1184 Maintenance,
1185 Offline,
1186}
1187
1188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1189pub struct NodeHostCapacity {
1190 pub max_clusters: u32,
1191 pub assigned_clusters: u32,
1192 pub storage_gib: u32,
1193 pub used_storage_gib: u32,
1194}
1195
1196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1197pub struct OperationRecord {
1198 pub operation_id: String,
1199 pub idempotency_key: String,
1200 pub target_resource_id: String,
1201 pub kind: OperationKind,
1202 pub status: OperationStatus,
1203 pub current_step: String,
1204 pub lease_owner: Option<String>,
1205}
1206
1207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1208#[serde(rename_all = "snake_case")]
1209pub enum OperationKind {
1210 CreateCluster,
1211 StartCluster,
1212 StopCluster,
1213 ResizeCluster,
1214 UpdateCluster,
1215 RotateCredentials,
1216 BackupCluster,
1217 DeleteBackup,
1218 ArchiveWalSegment,
1219 RestoreCluster,
1220 CreateDatabaseClone,
1221 PrepareStandby,
1222 CheckStandby,
1223 FencePrimary,
1224 FailoverCluster,
1225 DeleteCluster,
1226 StartSyncDeployment,
1227 StopSyncDeployment,
1228}
1229
1230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1231#[serde(rename_all = "snake_case")]
1232pub enum OperationStatus {
1233 Pending,
1234 Running,
1235 Succeeded,
1236 Failed,
1237 Cancelled,
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1241pub struct AuditEvent {
1242 pub event_id: String,
1243 pub actor_id: String,
1244 pub action: String,
1245 pub resource_id: String,
1246 pub occurred_at: String,
1247}
1248
1249#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1250pub struct ApiKey {
1251 pub key_id: String,
1252 pub token_prefix: String,
1253 pub name: String,
1254 pub organization_id: Option<String>,
1255 pub project_id: Option<String>,
1256 pub environment_id: Option<String>,
1257 pub role: TeamRole,
1258 pub created_by: String,
1259 pub revoked: bool,
1260}
1261
1262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1263pub struct TeamMembership {
1264 pub organization_id: String,
1265 pub actor_id: String,
1266 pub role: TeamRole,
1267}
1268
1269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1270pub struct SecretEncryptionKey {
1271 pub key_ref: String,
1272 pub provider: String,
1273 pub purpose: String,
1274 pub status: SecretEncryptionKeyStatus,
1275 pub created_at: String,
1276 pub activated_at: Option<String>,
1277 pub retired_at: Option<String>,
1278}
1279
1280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1281#[serde(rename_all = "snake_case")]
1282pub enum SecretEncryptionKeyStatus {
1283 Active,
1284 Retiring,
1285 Retired,
1286}
1287
1288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1289pub struct SecretRewrapPlan {
1290 pub plan_id: String,
1291 pub source_key_ref: String,
1292 pub target_key_ref: String,
1293 pub status: SecretRewrapPlanStatus,
1294 pub matched_secret_count: u32,
1295 pub rewrapped_secret_count: u32,
1296 pub error_message: Option<String>,
1297 pub created_at: String,
1298 pub completed_at: Option<String>,
1299}
1300
1301#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1302#[serde(rename_all = "snake_case")]
1303pub enum SecretRewrapPlanStatus {
1304 Planned,
1305 Running,
1306 Succeeded,
1307 Failed,
1308}
1309
1310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1311pub struct JwtIssuer {
1312 pub issuer_id: String,
1313 pub organization_id: String,
1314 pub project_id: Option<String>,
1315 pub environment_id: Option<String>,
1316 pub name: String,
1317 pub issuer: String,
1318 pub audience: String,
1319 pub jwks_url: String,
1320 pub claim_to_field: Vec<JwtClaimMapping>,
1321 pub status: JwtIssuerStatus,
1322}
1323
1324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1325pub struct WebhookEndpoint {
1326 pub endpoint_id: String,
1327 pub organization_id: String,
1328 pub project_id: Option<String>,
1329 pub environment_id: Option<String>,
1330 pub name: String,
1331 pub url: String,
1332 pub event_types: Vec<String>,
1333 pub signing_secret_ref: Option<SecretRef>,
1334 pub status: WebhookEndpointStatus,
1335}
1336
1337#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1338pub struct SsoIdentityProvider {
1339 pub provider_id: String,
1340 pub organization_id: String,
1341 pub name: String,
1342 pub kind: SsoProviderKind,
1343 pub issuer: String,
1344 pub sso_url: String,
1345 pub certificate_secret_ref: Option<SecretRef>,
1346 pub claim_mappings: Vec<SsoClaimMapping>,
1347 pub status: SsoProviderStatus,
1348}
1349
1350#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1351pub struct Incident {
1352 pub incident_id: String,
1353 pub organization_id: String,
1354 pub project_id: Option<String>,
1355 pub environment_id: Option<String>,
1356 pub title: String,
1357 pub summary: String,
1358 pub severity: IncidentSeverity,
1359 pub status: IncidentStatus,
1360 #[serde(default)]
1361 pub impacted_services: Vec<String>,
1362 pub started_at: String,
1363 pub resolved_at: Option<String>,
1364}
1365
1366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1367pub struct SsoClaimMapping {
1368 pub claim: String,
1369 pub field: String,
1370}
1371
1372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1373pub struct JwtClaimMapping {
1374 pub claim: String,
1375 pub field: String,
1376}
1377
1378#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1379#[serde(rename_all = "snake_case")]
1380pub enum TeamRole {
1381 Owner,
1382 Admin,
1383 Developer,
1384 Viewer,
1385 Ci,
1386}
1387
1388#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1389#[serde(rename_all = "snake_case")]
1390pub enum JwtIssuerStatus {
1391 Active,
1392 Disabled,
1393}
1394
1395#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1396#[serde(rename_all = "snake_case")]
1397pub enum WebhookEndpointStatus {
1398 Active,
1399 Disabled,
1400}
1401
1402#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1403#[serde(rename_all = "snake_case")]
1404pub enum SsoProviderKind {
1405 Saml,
1406 Oidc,
1407}
1408
1409#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1410#[serde(rename_all = "snake_case")]
1411pub enum SsoProviderStatus {
1412 Active,
1413 Disabled,
1414}
1415
1416#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1417#[serde(rename_all = "snake_case")]
1418pub enum IncidentSeverity {
1419 Info,
1420 Warning,
1421 Critical,
1422}
1423
1424#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1425#[serde(rename_all = "snake_case")]
1426pub enum IncidentStatus {
1427 Investigating,
1428 Identified,
1429 Monitoring,
1430 Resolved,
1431}
1432
1433#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1434pub struct UsageEvent {
1435 pub event_id: String,
1436 pub idempotency_key: String,
1437 pub organization_id: String,
1438 pub project_id: String,
1439 pub environment_id: String,
1440 pub metric: String,
1441 pub quantity: u64,
1442 pub occurred_at: String,
1443 #[serde(default, skip_serializing_if = "Option::is_none")]
1444 pub signature: Option<UsageEventSignature>,
1445}
1446
1447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1448pub struct UsageEventSignature {
1449 pub key_id: String,
1450 pub algorithm: String,
1451 pub signature: String,
1452}
1453
1454#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1455pub struct QuotaPolicy {
1456 pub policy_id: String,
1457 pub organization_id: String,
1458 pub project_id: String,
1459 pub environment_id: String,
1460 pub metric: String,
1461 pub limit_quantity: u64,
1462 pub window_seconds: u64,
1463 pub enforcement: QuotaEnforcement,
1464}
1465
1466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1467pub struct QuotaAlert {
1468 pub alert_id: String,
1469 pub policy_id: String,
1470 pub organization_id: String,
1471 pub project_id: String,
1472 pub environment_id: String,
1473 pub metric: String,
1474 pub threshold_basis_points: u32,
1475 pub current_quantity: u64,
1476 pub limit_quantity: u64,
1477 pub window_seconds: u64,
1478 pub state: QuotaAlertState,
1479 pub last_evaluated_at: Option<String>,
1480 pub fired_at: Option<String>,
1481 pub resolved_at: Option<String>,
1482}
1483
1484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1485pub struct QueryPermissionPolicy {
1486 pub policy_id: String,
1487 pub organization_id: String,
1488 pub project_id: String,
1489 pub environment_id: String,
1490 pub name: String,
1491 pub table_schema: String,
1492 pub table_name: String,
1493 pub operation: QueryPermissionOperation,
1494 pub principal_claim: String,
1495 pub predicate_sql: String,
1496 pub sample_context: serde_json::Value,
1497 pub status: QueryPermissionPolicyStatus,
1498}
1499
1500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1506pub struct PermissionRuleDocument {
1507 pub environment_id: String,
1508 pub organization_id: String,
1509 pub project_id: String,
1510 pub dsl: String,
1511 pub updated_at: Option<String>,
1512}
1513
1514#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1515#[serde(rename_all = "snake_case")]
1516pub enum QuotaEnforcement {
1517 Reject,
1518 Observe,
1519}
1520
1521#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1522#[serde(rename_all = "snake_case")]
1523pub enum QuotaAlertState {
1524 Ok,
1525 Firing,
1526}
1527
1528#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1529#[serde(rename_all = "snake_case")]
1530pub enum QueryPermissionOperation {
1531 Read,
1532 Subscribe,
1533}
1534
1535#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1536#[serde(rename_all = "snake_case")]
1537pub enum QueryPermissionPolicyStatus {
1538 Draft,
1539 Active,
1540}
1541
1542#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1543pub struct BillingExport {
1544 pub export_id: String,
1545 pub destination: String,
1546 pub delivery_ref: Option<String>,
1547 pub organization_id: Option<String>,
1548 pub project_id: Option<String>,
1549 pub environment_id: Option<String>,
1550 pub metric: Option<String>,
1551 pub occurred_at_from: Option<String>,
1552 pub occurred_at_to: Option<String>,
1553 pub event_count: u64,
1554 pub quantity_total: u64,
1555 pub status: BillingExportStatus,
1556 pub error_message: Option<String>,
1557 pub events: Vec<UsageEvent>,
1558}
1559
1560#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1561#[serde(rename_all = "snake_case")]
1562pub enum BillingExportStatus {
1563 Succeeded,
1564 Failed,
1565}
1566
1567#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1568pub struct SecretRef {
1569 pub secret_id: String,
1570 pub provider: String,
1571 pub external_ref: String,
1572}
1573
1574#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1575pub struct BackupStatus {
1576 pub last_base_backup_id: Option<String>,
1577 pub last_base_backup_at: Option<String>,
1578 pub last_successful_restore_check_at: Option<String>,
1579 pub pitr_window_hours: u16,
1580 pub state: BackupState,
1581}
1582
1583#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1584#[serde(rename_all = "snake_case")]
1585pub enum BackupState {
1586 Unknown,
1587 Healthy,
1588 Degraded,
1589 Failed,
1590}
1591
1592#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1593pub struct WalArchiveStatus {
1594 pub latest_archived_lsn: Option<String>,
1595 pub latest_archived_at: Option<String>,
1596 pub archive_lag_bytes: Option<u64>,
1597 pub state: WalArchiveState,
1598}
1599
1600#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1601#[serde(rename_all = "snake_case")]
1602pub enum WalArchiveState {
1603 Unknown,
1604 Streaming,
1605 Lagging,
1606 Failed,
1607}
1608
1609#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1610pub struct CustomerEnvironmentHealth {
1611 pub environment_id: String,
1612 pub state: CustomerEnvironmentHealthState,
1613 pub summary: String,
1614 pub components: Vec<CustomerEnvironmentHealthComponent>,
1615}
1616
1617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1618pub struct CustomerEnvironmentHealthComponent {
1619 pub name: String,
1620 pub state: CustomerEnvironmentHealthState,
1621 pub detail: String,
1622}
1623
1624#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1625#[serde(rename_all = "snake_case")]
1626pub enum CustomerEnvironmentHealthState {
1627 Healthy,
1628 Degraded,
1629 AtRisk,
1630 Maintenance,
1631 Unavailable,
1632}
1633
1634#[derive(Debug, Error, PartialEq, Eq)]
1635pub enum ModelError {
1636 #[error("invalid postgres version '{0}'")]
1637 InvalidPostgresVersion(String),
1638 #[error("postgres version '{version}' is unsupported; managed and local PaaS require PostgreSQL {minimum_major}+")]
1639 UnsupportedPostgresVersion { version: String, minimum_major: u16 },
1640 #[error("database mode {mode:?} requires postgres_version")]
1641 MissingPostgresVersion { mode: DatabaseMode },
1642 #[error("managed database mode requires a managed postgres spec")]
1643 MissingManagedPostgresSpec,
1644}
1645
1646#[cfg(test)]
1647mod tests {
1648 use super::*;
1649
1650 #[test]
1651 fn postgres_version_accepts_eighteen_and_newer() {
1652 let version = PostgresVersion::new("18.1").expect("version should parse");
1653 assert_eq!(version.major(), 18);
1654
1655 let version = PostgresVersion::new("19-beta1").expect("version should parse");
1656 assert_eq!(version.major(), 19);
1657 }
1658
1659 #[test]
1660 fn postgres_version_rejects_pre_eighteen() {
1661 let err = PostgresVersion::new("17.5").expect_err("version should be rejected");
1662 assert_eq!(
1663 err,
1664 ModelError::UnsupportedPostgresVersion {
1665 version: "17.5".to_owned(),
1666 minimum_major: 18
1667 }
1668 );
1669 }
1670
1671 #[test]
1672 fn managed_database_requires_version_and_spec() {
1673 let spec = DatabaseSpec {
1674 mode: DatabaseMode::Managed,
1675 postgres_version: Some(PostgresVersion::new("18").expect("valid version")),
1676 managed: None,
1677 };
1678
1679 assert_eq!(spec.validate(), Err(ModelError::MissingManagedPostgresSpec));
1680 }
1681
1682 #[test]
1683 fn managed_postgres_spec_roles_default_when_omitted() {
1684 let raw = r#"{
1685 "cluster_id": "cluster_123",
1686 "tier": "dev",
1687 "storage_gib": 20,
1688 "backup_policy": {
1689 "pitr_window_hours": 72,
1690 "base_backup_interval_hours": 24
1691 },
1692 "maintenance_policy": {
1693 "window": "sun:04:00-05:00Z",
1694 "auto_minor_upgrades": true
1695 }
1696 }"#;
1697
1698 let spec: ManagedPostgresSpec = serde_json::from_str(raw).expect("spec parses");
1699
1700 assert!(spec.roles.is_empty());
1701 }
1702}