Skip to main content

tfparser_core/ir/
environment.rs

1//! Workspace-wide environment values (`staging`, `production`, …).
2//!
3//! Per [00-prd.md] and [14-terragrunt.md], an environment carries the
4//! cascade-resolved AWS account/region/profile plus a `locals` map sourced
5//! from `terraform/environments/<env>.terragrunt.hcl`. The Terragrunt
6//! resolver populates this in Phase 6.
7//!
8//! [00-prd.md]: ../../specs/00-prd.md
9//! [14-terragrunt.md]: ../../specs/14-terragrunt.md
10
11use std::{path::Path, sync::Arc};
12
13use serde::{Deserialize, Serialize};
14use typed_builder::TypedBuilder;
15
16use crate::ir::{AccountId, Map, Region};
17
18/// A named environment (`staging`, `production`, etc.).
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
20#[non_exhaustive]
21#[serde(rename_all = "camelCase")]
22#[builder(field_defaults(setter(into)))]
23pub struct Environment {
24    /// Environment name (e.g. `staging`).
25    pub name: Arc<str>,
26
27    /// AWS account id (12 digits). `None` if the source did not declare one.
28    #[builder(default)]
29    pub aws_account_id: Option<AccountId>,
30
31    /// AWS region. `None` if the source did not declare one.
32    #[builder(default)]
33    pub aws_region: Option<Region>,
34
35    /// AWS shared-config profile name. `None` if the source did not declare
36    /// one.
37    #[builder(default)]
38    pub aws_profile: Option<Arc<str>>,
39
40    /// Path of the source file that defined this environment (typically
41    /// `terraform/environments/<name>.terragrunt.hcl`).
42    #[serde(with = "crate::ir::path_serde::arc_path")]
43    pub source_file: Arc<Path>,
44
45    /// Resolved environment-level locals (every other key from the source
46    /// file).
47    #[builder(default)]
48    pub locals: Map,
49}
50
51#[cfg(test)]
52#[allow(clippy::unwrap_used)]
53mod tests {
54    use std::path::PathBuf;
55
56    use super::*;
57    use crate::ir::{AccountId, Region};
58
59    #[test]
60    fn test_should_build_minimal_environment() {
61        let env = Environment::builder()
62            .name(Arc::<str>::from("staging"))
63            .source_file(Arc::<Path>::from(PathBuf::from(
64                "terraform/environments/staging.terragrunt.hcl",
65            )))
66            .build();
67        assert_eq!(&*env.name, "staging");
68        assert!(env.locals.is_empty());
69        assert!(env.aws_account_id.is_none());
70    }
71
72    #[test]
73    fn test_should_carry_validated_aws_metadata() {
74        let env = Environment::builder()
75            .name(Arc::<str>::from("production"))
76            .aws_account_id(Some(AccountId::new("100000000001").unwrap()))
77            .aws_region(Some(Region::new("us-west-2").unwrap()))
78            .aws_profile(Some(Arc::<str>::from("primary")))
79            .source_file(Arc::<Path>::from(PathBuf::from("a.hcl")))
80            .build();
81        assert_eq!(
82            env.aws_account_id.as_ref().map(AccountId::as_str),
83            Some("100000000001")
84        );
85        assert_eq!(
86            env.aws_region.as_ref().map(Region::as_str),
87            Some("us-west-2")
88        );
89    }
90
91    #[test]
92    fn test_should_serde_round_trip_environment() {
93        let env = Environment::builder()
94            .name(Arc::<str>::from("staging"))
95            .aws_account_id(Some(AccountId::new("100000000001").unwrap()))
96            .aws_region(Some(Region::new("us-west-2").unwrap()))
97            .source_file(Arc::<Path>::from(PathBuf::from("a.hcl")))
98            .build();
99        let json = serde_json::to_string(&env).unwrap();
100        let back: Environment = serde_json::from_str(&json).unwrap();
101        assert_eq!(env, back);
102    }
103}