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
use crate::config::Config;
use crate::error::Result;
use crate::secret_resolver;
use clap::Args;
use crate::commands::Cli;
#[derive(Debug, Args)]
#[command(visible_alias = "c")]
pub struct CheckCommand {
/// Check all secrets including those with if_missing=warn or if_missing=ignore
#[arg(short = 'a', long)]
all: bool,
}
impl CheckCommand {
pub async fn run(&self, cli: &Cli, config: Config) -> Result<()> {
config.validate()?;
let profile = Config::get_profile(cli.profile.as_deref());
// Load config
println!("Checking configuration for profile: {}", profile);
let mut issues = Vec::new();
let mut warnings = Vec::new();
// Check secrets
if let Ok(secrets) = config.get_secrets(&profile) {
if secrets.is_empty() {
warnings.push("No secrets defined in profile".to_string());
} else {
println!("Found {} secret(s) in profile", secrets.len());
for (name, secret_config) in secrets {
// Check if secret has a value source
if !secret_config.has_value() {
match secret_config.if_missing {
Some(crate::config::IfMissing::Error) => {
issues.push(format!(
"Secret '{}' is required but has no value source",
name
));
}
Some(crate::config::IfMissing::Warn) => {
warnings.push(format!("Secret '{}' has no value source", name));
}
_ => {
// Ignore is fine
}
}
}
// Check provider configuration
if let Some(provider) = secret_config.provider() {
let providers = config.get_providers(&profile);
if !providers.contains_key(provider) {
warnings.push(format!(
"Secret '{}' references unknown provider '{}'",
name, provider
));
} else {
// Determine if we should check this secret
let if_missing = secret_resolver::resolve_if_missing_behavior(
&secret_config,
&config,
);
// Skip checking if not --all and if_missing is not Error
if !self.all
&& matches!(
if_missing,
crate::config::IfMissing::Warn
| crate::config::IfMissing::Ignore
)
{
continue;
}
// Try to actually resolve the secret from the provider
match secret_resolver::resolve_secret(
&config,
&profile,
&name,
&secret_config,
)
.await
{
Ok(Some(_)) => {
// Secret resolved successfully
}
Ok(None) => {
// No value found, but that might be OK depending on if_missing
match if_missing {
crate::config::IfMissing::Error => {
issues.push(format!(
"Secret '{}' could not be resolved from provider '{}'",
name, provider
));
}
crate::config::IfMissing::Warn => {
warnings.push(format!(
"Secret '{}' could not be resolved from provider '{}'",
name, provider
));
}
crate::config::IfMissing::Ignore => {
// Silently ignore
}
}
}
Err(err) => {
// Error resolving secret
match if_missing {
crate::config::IfMissing::Error => {
issues.push(format!(
"Secret '{}' failed to resolve: {}",
name, err
));
}
crate::config::IfMissing::Warn => {
warnings.push(format!(
"Secret '{}' failed to resolve: {}",
name, err
));
}
crate::config::IfMissing::Ignore => {
// Silently ignore
}
}
}
}
}
}
}
}
} else {
issues.push(format!("Profile '{}' not found", profile));
}
// Check providers
let providers = config.get_providers(&profile);
if providers.is_empty() {
warnings.push("No providers configured".to_string());
} else {
println!("Found {} provider(s) in profile", providers.len());
}
// Report results
if !issues.is_empty() {
eprintln!("Found {} error(s):", issues.len());
for issue in &issues {
eprintln!(" {}", issue);
}
}
if !warnings.is_empty() {
eprintln!("Found {} warning(s):", warnings.len());
for warning in &warnings {
eprintln!(" {}", warning);
}
}
if issues.is_empty() && warnings.is_empty() {
println!("✓ Configuration is healthy");
} else if issues.is_empty() {
println!("✓ Configuration is OK (with warnings)");
}
if !issues.is_empty() {
std::process::exit(1);
}
Ok(())
}
}