Skip to main content

terraform_wrapper/commands/
providers.rs

1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// The providers subcommand to execute.
7#[derive(Debug, Clone)]
8pub enum ProvidersSubcommand {
9    /// List required providers for the configuration.
10    Default,
11    /// Write provider dependency lock file.
12    Lock,
13    /// Mirror providers to a local directory.
14    Mirror(String),
15    /// Output provider schemas as JSON.
16    Schema,
17}
18
19/// Command for listing and managing Terraform providers.
20///
21/// Supports several subcommands for inspecting and managing provider
22/// dependencies:
23///
24/// ```no_run
25/// # async fn example() -> terraform_wrapper::error::Result<()> {
26/// use terraform_wrapper::{Terraform, TerraformCommand};
27/// use terraform_wrapper::commands::providers::ProvidersCommand;
28///
29/// let tf = Terraform::builder().working_dir("/tmp/infra").build()?;
30///
31/// // List required providers
32/// let output = ProvidersCommand::new().execute(&tf).await?;
33///
34/// // Output provider schemas as JSON (useful for tooling)
35/// let output = ProvidersCommand::schema().execute(&tf).await?;
36///
37/// // Mirror providers to a local directory
38/// let output = ProvidersCommand::mirror("/tmp/providers")
39///     .platform("linux_amd64")
40///     .execute(&tf)
41///     .await?;
42/// # Ok(())
43/// # }
44/// ```
45#[derive(Debug, Clone)]
46pub struct ProvidersCommand {
47    subcommand: ProvidersSubcommand,
48    platforms: Vec<String>,
49    raw_args: Vec<String>,
50}
51
52impl ProvidersCommand {
53    /// List required providers (default subcommand).
54    #[must_use]
55    pub fn new() -> Self {
56        Self {
57            subcommand: ProvidersSubcommand::Default,
58            platforms: Vec::new(),
59            raw_args: Vec::new(),
60        }
61    }
62
63    /// Write the provider dependency lock file (`providers lock`).
64    #[must_use]
65    pub fn lock() -> Self {
66        Self {
67            subcommand: ProvidersSubcommand::Lock,
68            platforms: Vec::new(),
69            raw_args: Vec::new(),
70        }
71    }
72
73    /// Mirror providers to a local directory (`providers mirror <target_dir>`).
74    #[must_use]
75    pub fn mirror(target_dir: &str) -> Self {
76        Self {
77            subcommand: ProvidersSubcommand::Mirror(target_dir.to_string()),
78            platforms: Vec::new(),
79            raw_args: Vec::new(),
80        }
81    }
82
83    /// Output provider schemas as JSON (`providers schema -json`).
84    #[must_use]
85    pub fn schema() -> Self {
86        Self {
87            subcommand: ProvidersSubcommand::Schema,
88            platforms: Vec::new(),
89            raw_args: Vec::new(),
90        }
91    }
92
93    /// Target a specific platform (`-platform`). Can be called multiple times.
94    ///
95    /// Applies to `lock` and `mirror` subcommands.
96    #[must_use]
97    pub fn platform(mut self, platform: &str) -> Self {
98        self.platforms.push(platform.to_string());
99        self
100    }
101
102    /// Add a raw argument (escape hatch for unsupported options).
103    #[must_use]
104    pub fn arg(mut self, arg: impl Into<String>) -> Self {
105        self.raw_args.push(arg.into());
106        self
107    }
108}
109
110impl Default for ProvidersCommand {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl TerraformCommand for ProvidersCommand {
117    type Output = CommandOutput;
118
119    fn args(&self) -> Vec<String> {
120        let mut args = vec!["providers".to_string()];
121        match &self.subcommand {
122            ProvidersSubcommand::Default => {}
123            ProvidersSubcommand::Lock => {
124                args.push("lock".to_string());
125                for platform in &self.platforms {
126                    args.push(format!("-platform={platform}"));
127                }
128            }
129            ProvidersSubcommand::Mirror(target_dir) => {
130                args.push("mirror".to_string());
131                for platform in &self.platforms {
132                    args.push(format!("-platform={platform}"));
133                }
134                args.push(target_dir.clone());
135            }
136            ProvidersSubcommand::Schema => {
137                args.push("schema".to_string());
138                args.push("-json".to_string());
139            }
140        }
141        args.extend(self.raw_args.clone());
142        args
143    }
144
145    async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
146        exec::run_terraform(tf, self.args()).await
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn default_args() {
156        let cmd = ProvidersCommand::new();
157        assert_eq!(cmd.args(), vec!["providers"]);
158    }
159
160    #[test]
161    fn lock_args() {
162        let cmd = ProvidersCommand::lock();
163        assert_eq!(cmd.args(), vec!["providers", "lock"]);
164    }
165
166    #[test]
167    fn lock_with_platforms() {
168        let cmd = ProvidersCommand::lock()
169            .platform("linux_amd64")
170            .platform("darwin_arm64");
171        let args = cmd.args();
172        assert_eq!(args[0], "providers");
173        assert_eq!(args[1], "lock");
174        assert!(args.contains(&"-platform=linux_amd64".to_string()));
175        assert!(args.contains(&"-platform=darwin_arm64".to_string()));
176    }
177
178    #[test]
179    fn mirror_args() {
180        let cmd = ProvidersCommand::mirror("/tmp/providers");
181        assert_eq!(cmd.args(), vec!["providers", "mirror", "/tmp/providers"]);
182    }
183
184    #[test]
185    fn mirror_with_platform() {
186        let cmd = ProvidersCommand::mirror("/tmp/providers").platform("linux_amd64");
187        assert_eq!(
188            cmd.args(),
189            vec![
190                "providers",
191                "mirror",
192                "-platform=linux_amd64",
193                "/tmp/providers"
194            ]
195        );
196    }
197
198    #[test]
199    fn schema_args() {
200        let cmd = ProvidersCommand::schema();
201        assert_eq!(cmd.args(), vec!["providers", "schema", "-json"]);
202    }
203}