tf_bindgen_core/
stack.rs

1use std::borrow::{Borrow, BorrowMut};
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6use tf_bindgen_schema::document::{Meta, Metadata, ProviderConfig, Terraform};
7use tf_bindgen_schema::Document;
8
9use crate::{L1Construct, Path, Provider, Scope};
10
11/// Used to store and manage all resources and data sources associated with an infrastructure
12/// deployment.
13///
14/// Will use a mutable container to store resources and data sources.
15#[derive(Clone)]
16pub struct Stack {
17    inner: Rc<InnerStack>,
18}
19
20struct InnerStack {
21    name: String,
22    provider: RefCell<Vec<Rc<dyn Provider>>>,
23    resources: RefCell<Vec<Rc<dyn L1Construct>>>,
24    data_sources: RefCell<Vec<Rc<dyn L1Construct>>>,
25}
26
27impl Stack {
28    pub fn new(name: impl Into<String>) -> Rc<Self> {
29        Rc::new(Self {
30            inner: Rc::new(InnerStack {
31                name: name.into(),
32                provider: RefCell::new(Vec::new()),
33                resources: RefCell::new(Vec::new()),
34                data_sources: RefCell::new(Vec::new()),
35            }),
36        })
37    }
38
39    /// Name of the stack.
40    pub fn name(&self) -> &str {
41        &self.inner.name
42    }
43
44    /// Add `provider` to this stack.
45    pub fn add_provider(&self, provider: Rc<dyn Provider>) {
46        self.inner.provider.borrow_mut().push(provider)
47    }
48
49    /// Add `resource` to this stack.
50    pub fn add_resource(&self, resource: Rc<dyn L1Construct>) {
51        self.inner.resources.borrow_mut().push(resource)
52    }
53
54    /// Add a data source to the data source store.
55    pub fn add_data_source(&self, data_source: Rc<dyn L1Construct>) {
56        self.inner.data_sources.borrow_mut().push(data_source)
57    }
58
59    /// Generate Terraform JSON configuration out of stored provider, resources and data sources.
60    pub fn to_document(&self) -> Document {
61        let mut document = Document {
62            meta: Meta {
63                metadata: Metadata {
64                    backend: "local".to_string(),
65                    stack_name: self.name().to_string(),
66                    version: tf_bindgen_schema::VERSION.to_string(),
67                },
68                outputs: HashMap::default(),
69            },
70            terraform: Terraform {
71                required_providers: HashMap::default(),
72            },
73            provider: HashMap::default(),
74            resource: HashMap::default(),
75            data: HashMap::default(),
76        };
77        for provider in self.inner.provider.borrow().iter() {
78            let path = provider.path();
79            let name = path.name();
80            let local_name = name.split('/').last().unwrap();
81            let (version, schema) = provider.to_schema();
82            let config = ProviderConfig {
83                source: name.to_string(),
84                version,
85            };
86            document
87                .terraform
88                .required_providers
89                .insert(local_name.to_string(), config);
90            if !document.provider.borrow().contains_key(local_name) {
91                document
92                    .provider
93                    .borrow_mut()
94                    .insert(local_name.to_string(), Vec::new());
95            }
96            document
97                .provider
98                .borrow_mut()
99                .get_mut(local_name)
100                .unwrap()
101                .push(schema);
102        }
103        for resource in self.inner.resources.borrow().iter() {
104            let path = resource.path();
105            let key = path.id();
106            let (ty, schema) = resource.to_schema();
107            if !document.resource.contains_key(&ty) {
108                document.resource.insert(ty.clone(), HashMap::new());
109            }
110            if document
111                .resource
112                .get_mut(&ty)
113                .unwrap()
114                .insert(key, schema)
115                .is_some()
116            {
117                panic!("resource '{path}' already exists");
118            }
119        }
120        for data_source in self.inner.data_sources.borrow().iter() {
121            let path = data_source.path();
122            let key = path.id();
123            let (ty, schema) = data_source.to_schema();
124            if !document.data.contains_key(&ty) {
125                document.data.insert(ty.clone(), HashMap::new());
126            }
127            if document
128                .data
129                .get_mut(&ty)
130                .unwrap()
131                .insert(key, schema)
132                .is_some()
133            {
134                panic!("data source '{path}' already exists");
135            }
136        }
137        document
138    }
139}
140
141impl Scope for Stack {
142    /// Returns a copy of it self.
143    fn stack(&self) -> Stack {
144        self.clone()
145    }
146
147    fn path(&self) -> crate::Path {
148        Path::from(self.name().to_string())
149    }
150}