Skip to main content

tfparser_core/ir/
provider.rs

1//! Provider configuration IR — both the declaration ([`ProviderBlock`])
2//! and the per-resource reference ([`ProviderRef`]).
3//!
4//! Per [80-glossary.md]: "Provider block / Provider ref / Provider source"
5//! are three distinct things. They are three distinct types here.
6//!
7//! [80-glossary.md]: ../../specs/80-glossary.md
8
9use std::sync::Arc;
10
11use serde::{Deserialize, Serialize};
12use typed_builder::TypedBuilder;
13
14use crate::ir::{AttributeMap, Expression, Span};
15
16/// Optional `assume_role { role_arn = "..." }` sub-block of a provider.
17///
18/// Used by [16-provider-resolver.md § 4](../../specs/16-provider-resolver.md)
19/// to extract a cross-account account-id from the role ARN.
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypedBuilder)]
21#[non_exhaustive]
22#[serde(rename_all = "camelCase")]
23#[builder(field_defaults(setter(into)))]
24pub struct AssumeRole {
25    /// `role_arn` expression as written in source.
26    pub role_arn_expr: Expression,
27    /// Span of the `assume_role { ... }` block.
28    pub span: Span,
29}
30
31/// A `provider "aws" {}` declaration.
32///
33/// Field order matches [10-data-model.md § 2.2]. Construct via the generated
34/// [`ProviderBlockBuilder`].
35///
36/// `Debug` redacts `raw` past the first few keys to avoid logging
37/// potentially-sensitive provider configuration at INFO level. See
38/// [70-security.md § 5](../../specs/70-security.md).
39#[derive(Clone, PartialEq, Serialize, Deserialize, TypedBuilder)]
40#[non_exhaustive]
41#[serde(rename_all = "camelCase")]
42#[builder(field_defaults(setter(into)))]
43pub struct ProviderBlock {
44    /// Local name of the provider (e.g. `"aws"`).
45    pub local_name: Arc<str>,
46
47    /// Alias, if the block had `alias = "..."`.
48    #[builder(default)]
49    pub alias: Option<Arc<str>>,
50
51    /// Provider source address from `required_providers` (e.g.
52    /// `"hashicorp/aws"`), if known.
53    #[builder(default)]
54    pub source_addr: Option<Arc<str>>,
55
56    /// `region = ...` expression.
57    #[builder(default)]
58    pub region_expr: Option<Expression>,
59
60    /// `profile = ...` expression.
61    #[builder(default)]
62    pub profile_expr: Option<Expression>,
63
64    /// Optional `assume_role { ... }` sub-block.
65    #[builder(default)]
66    pub assume_role: Option<AssumeRole>,
67
68    /// Verbatim top-level attributes of the provider body.
69    #[builder(default)]
70    pub raw: AttributeMap,
71
72    /// Span of the opening `provider` keyword.
73    pub span: Span,
74}
75
76impl std::fmt::Debug for ProviderBlock {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        f.debug_struct("ProviderBlock")
79            .field("local_name", &self.local_name)
80            .field("alias", &self.alias)
81            .field("source_addr", &self.source_addr)
82            .field("region_expr", &self.region_expr.as_ref().map(|_| "<expr>"))
83            .field(
84                "profile_expr",
85                &self.profile_expr.as_ref().map(|_| "<expr>"),
86            )
87            .field(
88                "assume_role",
89                &self.assume_role.as_ref().map(|_| "<assume_role>"),
90            )
91            .field(
92                "raw",
93                &format!("<{} attributes (redacted)>", self.raw.len()),
94            )
95            .field("span", &self.span)
96            .finish()
97    }
98}
99
100/// A resource-side reference to a provider via `provider = aws.<alias>`.
101///
102/// `alias = None` means the default `aws` provider was used.
103#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, TypedBuilder)]
104#[non_exhaustive]
105#[serde(rename_all = "camelCase")]
106#[builder(field_defaults(setter(into)))]
107pub struct ProviderRef {
108    /// Local name of the provider (e.g. `"aws"`).
109    pub local_name: Arc<str>,
110
111    /// Alias, if the reference had one (e.g. `"main"`).
112    #[builder(default)]
113    pub alias: Option<Arc<str>>,
114
115    /// Source span of the `provider = ...` attribute.
116    pub span: Span,
117}
118
119#[cfg(test)]
120#[allow(clippy::unwrap_used)]
121mod tests {
122    use std::sync::Arc;
123
124    use super::*;
125    use crate::ir::Span;
126
127    #[test]
128    fn test_should_build_minimal_provider_block() {
129        let p = ProviderBlock::builder()
130            .local_name(Arc::<str>::from("aws"))
131            .span(Span::synthetic())
132            .build();
133        assert_eq!(&*p.local_name, "aws");
134        assert!(p.alias.is_none());
135        assert!(p.raw.is_empty());
136    }
137
138    #[test]
139    fn test_should_redact_raw_attrs_in_debug() {
140        let p = ProviderBlock::builder()
141            .local_name(Arc::<str>::from("aws"))
142            .raw(vec![(
143                Arc::<str>::from("secret_access_key"),
144                crate::ir::Expression::Literal(crate::ir::Value::Str(Arc::<str>::from(
145                    "very-secret",
146                ))),
147            )])
148            .span(Span::synthetic())
149            .build();
150        let debug = format!("{p:?}");
151        assert!(!debug.contains("very-secret"), "{debug}");
152        assert!(debug.contains("redacted"), "{debug}");
153    }
154
155    #[test]
156    fn test_should_serde_round_trip_provider_ref() {
157        let r = ProviderRef {
158            local_name: Arc::<str>::from("aws"),
159            alias: Some(Arc::<str>::from("main")),
160            span: Span::synthetic(),
161        };
162        let json = serde_json::to_string(&r).unwrap();
163        let back: ProviderRef = serde_json::from_str(&json).unwrap();
164        assert_eq!(r, back);
165    }
166}