Skip to content

Commit 9de1530

Browse files
author
Alexander Weber
committed
added capability for temporary files
1 parent 20761be commit 9de1530

File tree

4 files changed

+97
-27
lines changed

4 files changed

+97
-27
lines changed

crates/secenv/src/args.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub(crate) enum Command {
5858
manifest: Manifest,
5959
profile_name: String,
6060
command: Option<Vec<String>>,
61+
force: bool,
6162
},
6263
Init {
6364
path: PathBuf,
@@ -131,6 +132,13 @@ impl ClapArgumentLoader {
131132
.required(false)
132133
.default_value("default"),
133134
)
135+
.arg(
136+
clap::Arg::new("force")
137+
.short('f')
138+
.long("force")
139+
.action(clap::ArgAction::SetTrue)
140+
.help("Overwrite existing files defined in the manifest"),
141+
)
134142
.arg(
135143
clap::Arg::new("command")
136144
.help("Command to execute with environment variables set")
@@ -204,11 +212,13 @@ impl ClapArgumentLoader {
204212
let command = subc
205213
.get_many::<String>("command")
206214
.map(|values| values.cloned().collect::<Vec<String>>());
215+
let force = subc.get_flag("force");
207216

208217
Command::Unlock {
209218
manifest: cfg,
210219
profile_name: profile_name.clone(),
211220
command,
221+
force,
212222
}
213223
} else if let Some(subc) = command.subcommand_matches("init") {
214224
let config_path = Self::get_absolute_path(subc, "path")?;

crates/secenv/src/main.rs

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use {
1515
std::{
1616
collections::HashMap,
1717
process::Command,
18+
path::Path,
1819
},
1920
};
2021

@@ -42,11 +43,7 @@ async fn main() -> Result<()> {
4243
crate::reference::build_shell_completion(&path, &shell)?;
4344
Ok(())
4445
},
45-
| crate::args::Command::Unlock {
46-
manifest,
47-
profile_name,
48-
command,
49-
} => {
46+
| crate::args::Command::Unlock { manifest, profile_name, command, force } => {
5047
let mut env_vars = HashMap::new();
5148
let mut pgp_manager = crate::pgp::PgpManager::new().context("Failed to initialize PGP manager")?;
5249

@@ -56,7 +53,7 @@ async fn main() -> Result<()> {
5653
.with_context(|| format!("Profile '{}' not found in manifest", profile_name))?;
5754

5855
for (key, value) in profile.env.vars.iter() {
59-
match value.get_value(&mut pgp_manager) {
56+
match value.inner.get_value(&mut pgp_manager) {
6057
| Ok(val) => {
6158
env_vars.insert(key.clone(), val);
6259
},
@@ -67,16 +64,68 @@ async fn main() -> Result<()> {
6764
}
6865
}
6966

70-
match command {
67+
// Prepare files from manifest before running command
68+
let mut created_files: Vec<String> = Vec::new();
69+
for (file_path, content) in profile.files.iter() {
70+
let absolute_path = Path::new(file_path);
71+
if absolute_path.exists() && !force {
72+
return Err(anyhow::anyhow!(
73+
"File '{}' already exists. Use --force to overwrite.",
74+
absolute_path.display()
75+
));
76+
}
77+
78+
let value = content.inner.get_value(&mut pgp_manager)?;
79+
if let Some(parent) = absolute_path.parent() {
80+
std::fs::create_dir_all(parent)
81+
.with_context(|| format!("Failed to create directories for {}", absolute_path.display()))?;
82+
}
83+
std::fs::write(&absolute_path, value)
84+
.with_context(|| format!("Failed to write file: {}", absolute_path.display()))?;
85+
created_files.push(absolute_path.to_string_lossy().to_string());
86+
}
87+
88+
let exec_status: Result<Option<std::process::ExitStatus>> = match command {
7189
| Some(cmd_args) if !cmd_args.is_empty() => {
72-
execute_command_with_env(&cmd_args, &env_vars, &profile.env.keep)
90+
let status = execute_command_with_env(&cmd_args, &env_vars, &profile.env.keep)?;
91+
Ok(Some(status))
7392
},
7493
| _ => {
7594
for (key, value) in env_vars {
7695
println!("{}={}", key, value);
7796
}
97+
Ok(None)
98+
},
99+
};
100+
101+
// Cleanup files after command execution or printing envs
102+
let mut cleanup_error: Option<anyhow::Error> = None;
103+
for file in created_files.iter() {
104+
if let Err(e) = std::fs::remove_file(file) {
105+
cleanup_error = Some(anyhow::anyhow!(
106+
"Failed to remove file '{}': {}",
107+
file,
108+
e
109+
));
110+
}
111+
}
112+
113+
if let Some(err) = cleanup_error {
114+
eprintln!("{}", err);
115+
}
116+
117+
match exec_status? {
118+
| Some(status) => {
119+
if !status.success() {
120+
if let Some(code) = status.code() {
121+
std::process::exit(code);
122+
} else {
123+
std::process::exit(1);
124+
}
125+
}
78126
Ok(())
79127
},
128+
| None => Ok(()),
80129
}
81130
},
82131
| args::Command::Init { path, force } => {
@@ -144,7 +193,7 @@ fn execute_command_with_env(
144193
cmd_args: &[String],
145194
env_vars: &HashMap<String, String>,
146195
keep_env_vars: &Option<Vec<String>>,
147-
) -> Result<()> {
196+
) -> Result<std::process::ExitStatus> {
148197
if cmd_args.is_empty() {
149198
return Err(anyhow::anyhow!("No command provided"));
150199
}
@@ -187,13 +236,5 @@ fn execute_command_with_env(
187236
.status()
188237
.with_context(|| format!("Failed to execute command: {}", program))?;
189238

190-
if !status.success() {
191-
if let Some(code) = status.code() {
192-
std::process::exit(code);
193-
} else {
194-
std::process::exit(1);
195-
}
196-
}
197-
198-
Ok(())
239+
Ok(status)
199240
}

crates/secenv/src/manifest.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,33 +70,45 @@ pub struct SecretWrapper {
7070
#[serde(rename_all = "snake_case")]
7171
pub struct Manifest {
7272
pub version: String,
73+
#[serde(default)]
7374
pub profiles: HashMap<String, ManifestProfile>,
7475
}
7576

7677
#[derive(Debug, Clone, Serialize, Deserialize)]
7778
#[serde(rename_all = "snake_case")]
7879
pub struct ManifestProfile {
80+
#[serde(default)]
81+
pub files: HashMap<String, ContentWrapper>,
82+
#[serde(default)]
7983
pub env: ManifestEnv,
8084
}
8185

82-
#[derive(Debug, Clone, Serialize, Deserialize)]
86+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8387
#[serde(rename_all = "snake_case")]
8488
pub struct ManifestEnv {
8589
#[serde(default)]
8690
pub keep: Option<Vec<String>>,
87-
pub vars: HashMap<String, ManifestEnvVar>,
91+
#[serde(default)]
92+
pub vars: HashMap<String, ContentWrapper>,
8893
}
8994

9095
#[derive(Debug, Clone, Serialize, Deserialize)]
9196
#[serde(rename_all = "snake_case")]
92-
pub enum ManifestEnvVar {
97+
pub enum Content {
9398
Plain(EncodedValue),
9499
Secure {
95100
secret: SecretWrapper,
96101
value: EncodedValueWrapper,
97102
},
98103
}
99104

105+
#[derive(Debug, Clone, Serialize, Deserialize)]
106+
#[serde(rename_all = "snake_case")]
107+
pub struct ContentWrapper {
108+
#[serde(flatten)]
109+
pub inner: Content,
110+
}
111+
100112
impl EncodedValue {
101113
pub fn get_value(&self) -> Result<String, anyhow::Error> {
102114
match self {
@@ -139,11 +151,11 @@ impl SecretAllocation {
139151
}
140152
}
141153

142-
impl ManifestEnvVar {
154+
impl Content {
143155
pub fn get_value(&self, pgp_manager: &mut crate::pgp::PgpManager) -> Result<String, anyhow::Error> {
144156
match self {
145-
| ManifestEnvVar::Plain(encoded_value) => encoded_value.get_value(),
146-
| ManifestEnvVar::Secure { secret, value } => {
157+
| Content::Plain(encoded_value) => encoded_value.get_value(),
158+
| Content::Secure { secret, value } => {
147159
let encrypted_data = value.inner.get_value()?;
148160

149161
match &secret.inner {

secenv.conf

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
version = "0.0.0"
22

3-
vars.secret = {
3+
vars.secret {
44
pgp.gpg.fingerprint = "1E1BAC706C352094D490D5393F5167F1F3002043"
55
}
66

77
profiles {
88
default {
9+
files {
10+
"./secret.key" {
11+
secure {
12+
secret = ${vars.secret}
13+
value.base64 = "LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoRjREclVCb0ttMWo1ZjhTQVFkQU8zL0p4RENQL1pWWHN0MGtXM0Zybmpydm5OT2Jrb0JXUldrcDVBbmJHMU13Ci9TK0kybE1ObW81MDQ0aDFYNGYyWTRneVM2SnhLbGkyYWJWSVFVUEVNVENLckR0dHhVTkFUNWp2cFk3amo0dlgKMGtZQktCLzc5dHJXdnNTd0hhMG5tbVJHcGlzUVp0SnFtYmNPM04wSkd3Kzl1STRYMW5kUE50S1JhWUJxRDd2QQpGdHk1TXNLNExjWHQ4NHNUUFBjOG0yVE85UzRtSzRoTAo9bHdMYgotLS0tLUVORCBQR1AgTUVTU0FHRS0tLS0tCg=="
14+
}
15+
}
16+
}
917
env.vars {
1018
SECRET_TOKEN.secure {
1119
secret = ${vars.secret}
1220
value.base64 = "LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoRjREclVCb0ttMWo1ZjhTQVFkQU8zL0p4RENQL1pWWHN0MGtXM0Zybmpydm5OT2Jrb0JXUldrcDVBbmJHMU13Ci9TK0kybE1ObW81MDQ0aDFYNGYyWTRneVM2SnhLbGkyYWJWSVFVUEVNVENLckR0dHhVTkFUNWp2cFk3amo0dlgKMGtZQktCLzc5dHJXdnNTd0hhMG5tbVJHcGlzUVp0SnFtYmNPM04wSkd3Kzl1STRYMW5kUE50S1JhWUJxRDd2QQpGdHk1TXNLNExjWHQ4NHNUUFBjOG0yVE85UzRtSzRoTAo9bHdMYgotLS0tLUVORCBQR1AgTUVTU0FHRS0tLS0tCg=="
1321
}
14-
15-
APP_NAME.plain.literal = "myapp"
1622
}
23+
env.vars.APP_NAME.plain.literal = "myapp"
1724
}
1825
}

0 commit comments

Comments
 (0)