1use std::fs::{read_dir, read_to_string};
2use std::process::exit;
3use clap::Parser;
4use clap::Subcommand;
5use tokio::fs::remove_file;
6use tokio::process::Command;
7use rusty_cdk::{deploy, destroy, diff};
8use rusty_cdk::stack::Stack;
9use rusty_cdk::wrappers::StringWithOnlyAlphaNumericsAndHyphens;
10
11const CURRENT_DIR: &'static str = ".";
12
13#[derive(Clone, Debug, Subcommand)]
14pub enum RustyCommand {
15 #[clap(about = "Deploy a stack")]
16 Deploy {
17 #[clap(short, long)]
19 name: String,
20 #[clap(short, long)]
23 synth_path: Option<String>,
24 #[clap(short, long)]
26 cleanup: bool
27 },
28 #[clap(about = "Generate diff with a deployed template with the given name")]
29 Diff {
30 #[clap(short, long)]
32 name: String,
33 #[clap(short, long)]
36 synth_path: Option<String>,
37 #[clap(short, long, default_missing_value="false")]
39 cleanup: bool
40 },
41 #[clap(about = "Destroy a stack with the give name")]
42 Destroy {
43 name: String,
45 #[clap(short, long, default_missing_value="false")]
48 force: bool
49 },
50}
51
52#[derive(Parser, Debug)]
53#[command(version, about, long_about = None)]
54pub struct Args {
55 #[command(subcommand)]
56 pub command: RustyCommand,
57}
58
59pub async fn entry_point(command: RustyCommand) {
60 match command {
61 RustyCommand::Deploy { name, synth_path, cleanup } => {
62 println!("deploying stack with name {name}");
63
64 let path = if let Some(path) = synth_path {
65 path
66 } else {
67 match run_synth_in_current_path().await {
68 Ok(path) => path,
69 Err(e) => {
70 eprintln!("{e}");
71 exit(1);
72 }
73 }
74 };
75 match get_path_as_stack(&path) {
76 Ok(stack) => deploy(StringWithOnlyAlphaNumericsAndHyphens(name), stack).await,
77 Err(e) => {
78 eprintln!("{e}");
79 exit(1);
80 }
81 }
82
83 if cleanup {
84 remove_fill_or_exit(&path).await;
85 }
86 }
87 RustyCommand::Diff { name, synth_path, cleanup } => {
88 println!("creating a diff with an existing stack (name {name})");
89
90 let path = if let Some(path) = synth_path {
91 path
92 } else {
93 match run_synth_in_current_path().await {
94 Ok(path) => path,
95 Err(e) => {
96 eprintln!("{e}");
97 exit(1);
98 }
99 }
100 };
101 match get_path_as_stack(&path) {
102 Ok(stack) => diff(StringWithOnlyAlphaNumericsAndHyphens(name), stack).await,
103 Err(e) => {
104 eprintln!("{e}");
105 exit(1);
106 }
107 }
108
109 if cleanup {
110 remove_fill_or_exit(&path).await;
111 }
112 }
113 RustyCommand::Destroy { name, force } => {
114 println!("destroying stack with name {name}");
115 destroy(StringWithOnlyAlphaNumericsAndHyphens(name)).await;
116 }
117 }
118}
119
120async fn run_synth_in_current_path() -> Result<String, String> {
122 let dir_content = read_dir(CURRENT_DIR);
123
124 match dir_content {
125 Ok(content) => {
126 let is_rust_project = content
127 .flat_map(|entry| entry.ok())
128 .any(|entry| {
129 entry.file_name() == "Cargo.toml" && entry.file_type().map(|f| f.is_file()).unwrap_or(false)
130 });
131
132 if is_rust_project {
133 match Command::new("sh")
134 .args(&["-c", "cargo run > cargo-rusty-temporary-synth.json"])
135 .output()
136 .await {
137 Ok(_) => Ok("./cargo-rusty-temporary-synth.json".to_string()),
138 Err(e) => {
139 Err(format!("Could not run `cargo run` (required to synth when no synth_path is passed in): {e}"))
140 }
141 }
142 } else {
143 Err("current dir does not seem to be a cargo project, could not find a Cargo.toml (required to synth when no synth_path is passed in)".to_string())
144 }
145 }
146 Err(e) => {
147 Err(format!("could not read dir: {e}"))
148 }
149 }
150}
151
152fn get_path_as_stack(path: &str) -> Result<Stack, String> {
153 match read_to_string(path) {
154 Ok(as_string) => {
155 match serde_json::from_str::<Stack>(&as_string) {
156 Ok(stack) => Ok(stack),
157 Err(e) => Err(format!("content of file {path} could not be read as a `Stack` (is there non-json content present?): {e}")),
158 }
159 }
160 Err(e) => Err(format!("could not read file with path {path}: {e}")),
161 }
162}
163
164async fn remove_fill_or_exit(path: &String) {
165 let removed = remove_file(&path).await;
166
167 if let Err(e) = removed {
168 eprintln!("error cleaning up file at {path}: {e}");
169 exit(1);
170 }
171}