Skip to main content

tatara_core/domain/
source.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7#[serde(rename_all = "snake_case")]
8pub enum SourceKind {
9    /// A git-hosted Nix flake (e.g., "github:pleme-io/tatara-infra")
10    GitFlake,
11    /// A direct flake output reference (e.g., "path:/nix/store/...")
12    FlakeOutput,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16#[serde(rename_all = "snake_case")]
17pub enum SourceStatus {
18    Pending,
19    Ready,
20    Failed,
21    Suspended,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Source {
26    pub id: Uuid,
27    pub name: String,
28    pub kind: SourceKind,
29    /// Flake reference, e.g., "github:pleme-io/tatara-infra"
30    pub flake_ref: String,
31    pub status: SourceStatus,
32    /// Last observed flake revision (git commit hash).
33    pub last_rev: Option<String>,
34    pub last_reconciled_at: Option<DateTime<Utc>>,
35    pub last_error: Option<String>,
36    /// Map of job_name -> spec content hash for managed jobs.
37    #[serde(default)]
38    pub managed_jobs: HashMap<String, String>,
39    pub created_at: DateTime<Utc>,
40}
41
42impl Source {
43    pub fn new(name: String, kind: SourceKind, flake_ref: String) -> Self {
44        Self {
45            id: Uuid::new_v4(),
46            name,
47            kind,
48            flake_ref,
49            status: SourceStatus::Pending,
50            last_rev: None,
51            last_reconciled_at: None,
52            last_error: None,
53            managed_jobs: HashMap::new(),
54            created_at: Utc::now(),
55        }
56    }
57}
58
59/// Request to create a source.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct CreateSourceRequest {
62    pub name: String,
63    pub flake_ref: String,
64    #[serde(default = "default_kind")]
65    pub kind: SourceKind,
66}
67
68fn default_kind() -> SourceKind {
69    SourceKind::GitFlake
70}
71
72/// Metadata returned by `nix flake metadata`.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct FlakeMetadata {
75    /// Git commit hash (None for path flakes).
76    pub rev: Option<String>,
77    /// Last modified timestamp (unix epoch).
78    pub last_modified: u64,
79    /// Resolved URL.
80    pub url: String,
81}
82
83/// Structured errors for source reconciliation.
84#[derive(Debug, thiserror::Error)]
85pub enum SourceError {
86    /// Failed to fetch flake metadata (network, auth, invalid ref).
87    #[error("metadata fetch failed for '{flake_ref}': {reason}")]
88    MetadataFetchFailed { flake_ref: String, reason: String },
89
90    /// Failed to evaluate tataraJobs from the flake.
91    #[error("eval failed for '{flake_ref}': {reason}")]
92    EvalFailed { flake_ref: String, reason: String },
93
94    /// Source validation failed (missing outputs, bad structure).
95    #[error("validation failed for source '{name}': {errors:?}")]
96    ValidationFailed { name: String, errors: Vec<String> },
97
98    /// Failed to apply a job change (create/update/delete).
99    #[error("job operation failed for '{job_name}' in source '{source_name}': {reason}")]
100    JobOperationFailed {
101        source_name: String,
102        job_name: String,
103        reason: String,
104    },
105
106    /// Timeout during a nix operation.
107    #[error("operation timed out for '{flake_ref}' after {timeout_secs}s")]
108    Timeout {
109        flake_ref: String,
110        timeout_secs: u64,
111    },
112}