-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcli.rs
More file actions
218 lines (206 loc) · 6.78 KB
/
cli.rs
File metadata and controls
218 lines (206 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use clap::{Args as ClapArgs, Parser, Subcommand};
use crate::types::CloudProvider;
#[derive(Parser, Debug)]
pub struct Args {
#[clap(subcommand)]
pub command: SubCommand,
}
#[derive(Subcommand, Debug)]
pub enum SubCommand {
/// Copies the example terraform code to a subdirectory,
/// creates a new terraform.tfvars.json, and runs `terraform init`.
Init {
#[clap(subcommand)]
provider: Box<InitProvider>,
},
/// Runs `terraform apply` for an already initialized test environment.
Apply {
/// Which test run to apply.
#[arg(long)]
test_run: String,
},
/// Runs verification commands against an already applied test environment.
Verify {
/// Which test run to verify.
#[arg(long)]
test_run: String,
},
/// Lists test runs, sorted by creation date.
List {
/// Only print the most recent test run.
#[arg(long)]
latest: bool,
},
/// Re-copies example .tf files into an already initialized test run,
/// picking up any local changes to the terraform code.
Sync {
/// Which test run to sync.
#[arg(long)]
test_run: String,
},
/// Runs `terraform destroy` against an already initialized test environment.
Destroy {
/// Which test run to destroy.
#[arg(long)]
test_run: String,
/// Remove the test run directory after successful destroy.
#[arg(long)]
rm: bool,
},
/// Runs the full test lifecycle: init, apply, verify, destroy.
Run {
#[clap(subcommand)]
provider: Box<InitProvider>,
/// Run `terraform destroy` even if apply or verify fails.
#[arg(long)]
destroy_on_failure: bool,
},
}
#[derive(ClapArgs, Debug)]
pub struct CommonInitArgs {
/// Value for the Owner tag/label applied to all resources.
#[arg(long)]
pub owner: String,
/// Value for the Purpose tag/label applied to all resources.
#[arg(long, default_value = "Integration test")]
pub purpose: String,
/// Materialize license key (conflicts with --license-key-file).
#[arg(
long,
env = "MATERIALIZE_LICENSE_KEY",
hide_env_values = true,
conflicts_with = "license_key_file",
required_unless_present = "license_key_file"
)]
pub license_key: Option<String>,
/// Path to a file containing the Materialize license key (conflicts with --license-key).
#[arg(long, conflicts_with = "license_key")]
pub license_key_file: Option<PathBuf>,
/// Path to a local orchestratord Helm chart directory. When set, automatically
/// injects helm_chart / use_local_chart into the operator module, creates
/// dev_variables.tf, and sets the corresponding tfvars values.
#[arg(long)]
pub local_chart_path: Option<PathBuf>,
/// Orchestratord image version.
#[arg(long)]
pub orchestratord_version: Option<String>,
/// Environmentd image version.
#[arg(long)]
pub environmentd_version: Option<String>,
/// S3 bucket for remote terraform state. If omitted, state is stored locally.
#[arg(long)]
pub backend_s3_bucket: Option<String>,
/// S3 region for the remote terraform state bucket. Required when --backend-s3-bucket is set.
#[arg(long, default_value = "us-east-1")]
pub backend_s3_region: String,
/// AWS profile for S3 backend authentication.
#[arg(long)]
pub backend_s3_profile: Option<String>,
}
/// Configuration for an S3 remote backend.
pub struct S3BackendConfig<'a> {
pub bucket: &'a str,
pub region: &'a str,
pub profile: Option<&'a str>,
}
impl CommonInitArgs {
/// Resolves the license key from either `--license-key` or `--license-key-file`.
pub fn resolve_license_key(&self) -> Result<String> {
if let Some(key) = &self.license_key {
return Ok(key.clone());
}
if let Some(path) = &self.license_key_file {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read license key file: {}", path.display()))?;
return Ok(content.trim().to_string());
}
bail!("Either --license-key or --license-key-file must be provided")
}
/// Returns the S3 backend configuration if `--backend-s3-bucket` is set.
pub fn s3_backend(&self) -> Option<S3BackendConfig<'_>> {
let bucket = self.backend_s3_bucket.as_deref()?;
Some(S3BackendConfig {
bucket,
region: &self.backend_s3_region,
profile: self.backend_s3_profile.as_deref(),
})
}
}
#[derive(Subcommand, Debug)]
pub enum InitProvider {
/// Initialize a test run on AWS.
Aws {
#[clap(flatten)]
common: CommonInitArgs,
/// AWS region.
#[arg(long)]
aws_region: String,
/// AWS profile for authentication.
#[arg(long)]
aws_profile: String,
},
/// Initialize a test run on Azure.
Azure {
#[clap(flatten)]
common: CommonInitArgs,
/// Azure subscription ID.
#[arg(long)]
subscription_id: String,
/// Azure resource group name. Defaults to the test run ID if omitted.
#[arg(long)]
resource_group_name: Option<String>,
/// Azure location.
#[arg(long)]
location: String,
},
/// Initialize a test run on GCP.
Gcp {
#[clap(flatten)]
common: CommonInitArgs,
/// GCP project ID.
#[arg(long)]
project_id: String,
/// GCP region.
#[arg(long)]
region: String,
},
}
impl InitProvider {
pub fn cloud_provider(&self) -> CloudProvider {
match self {
InitProvider::Aws { .. } => CloudProvider::Aws,
InitProvider::Azure { .. } => CloudProvider::Azure,
InitProvider::Gcp { .. } => CloudProvider::Gcp,
}
}
pub fn common(&self) -> &CommonInitArgs {
match self {
InitProvider::Aws { common, .. }
| InitProvider::Azure { common, .. }
| InitProvider::Gcp { common, .. } => common,
}
}
/// Returns the content for a `backend.tf` file if an S3 backend is
/// configured via `--backend-s3-bucket`, or `None` for local state.
pub fn backend_config(&self, test_run_id: &str) -> Option<String> {
let cfg = self.common().s3_backend()?;
let bucket = cfg.bucket;
let region = cfg.region;
let profile_line = cfg
.profile
.map(|p| format!("\n profile = \"{p}\""))
.unwrap_or_default();
Some(format!(
r#"terraform {{
backend "s3" {{
bucket = "{bucket}"
key = "{test_run_id}/terraform.tfstate"
region = "{region}"{profile_line}
}}
}}
"#
))
}
}