Skip to content

Commit 2510f36

Browse files
cacama-valvatadetjensrobert
authored andcommitted
big pile of junk: implement all functions which return init_vars back to run()
1 parent 79edd1f commit 2510f36

File tree

5 files changed

+303
-30
lines changed

5 files changed

+303
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ docker_credential = "1.3.2"
4444

4545
# interactive:
4646
inquire = "0.7.5"
47+
regex = "1.11.1"
4748

4849

4950
[dev-dependencies]

src/cli.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,13 @@ pub enum Commands {
9191
/// Copy an initial rcds.yaml to the current working directory.
9292
///
9393
/// If interactive is enabled, then it will prompt for the various fields of the config file. If left disabled, then it will copy it out with fake data of the expected format.
94+
///
95+
/// If blank is enabled, then it will copy out the file without any fields set. If left disabled, it will write the default non-interactive example config to file.
9496
Init {
9597
/// Guided filling out of the config
9698
#[arg(short = 'i', long)]
97-
interactive: bool
99+
interactive: bool,
100+
#[arg(short = 'b', long)]
101+
blank: bool
98102
}
99103
}

src/commands/init.rs

Lines changed: 293 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,63 @@
11
use anyhow::Context;
22
use inquire;
3+
use regex::Regex;
4+
use serde::Serialize;
5+
use std::fmt;
6+
7+
use crate::{access_handlers::frontend, commands::deploy};
38

49
use crate::utils::render_strict;
510

11+
#[derive(Debug, Serialize)]
612
struct InitVars {
7-
//
13+
flag_regex: String, // TODO: make all of these `str`s if it compiles
14+
registry_domain: String,
15+
registry_build_user: String,
16+
registry_build_pass: String,
17+
registry_cluster_user: String,
18+
registry_cluster_pass: String,
19+
defaults_difficulty: String, //u64,
20+
defaults_resources_cpu: String, //u64,
21+
defaults_resources_memory: String, //(u64, Option(String)),
22+
points: Vec<Points>,
23+
profiles: Vec<Profile>,
24+
}
25+
26+
#[derive(Clone, Debug, Serialize)]
27+
struct Points {
28+
difficulty: String, //u64,
29+
min: String, //u64,
30+
max: String, //u64
31+
}
32+
33+
impl fmt::Display for Points {
34+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35+
write!(
36+
f,
37+
"({} Points: {}-{})",
38+
self.difficulty, self.min, self.max
39+
)
40+
}
841
}
942

10-
pub fn run(interactive: &bool) -> Result<(), Box<dyn std::error::Error>> {
43+
#[derive(Debug, Serialize)]
44+
struct Profile {
45+
profile_name: String,
46+
frontend_url: String,
47+
frontend_token: String,
48+
challenges_domain: String,
49+
kubecontext: String,
50+
s3_endpoint: String,
51+
s3_region: String,
52+
s3_accesskey: String,
53+
s3_secretaccesskey: String,
54+
}
55+
56+
pub fn run(interactive: &bool, blank: &bool) -> Result<(), Box<dyn std::error::Error>> {
1157
let options = if *interactive {
1258
interactive_init().context("error in interactive init")?
59+
} else if *blank {
60+
blank_init()
1361
} else {
1462
noninteractive_init()
1563
};
@@ -18,7 +66,7 @@ pub fn run(interactive: &bool) -> Result<(), Box<dyn std::error::Error>> {
1866
let rendered = render_strict(
1967
CONFIG_TEMPLATE,
2068
minijinja::context! {
21-
..options
69+
options
2270
},
2371
)?;
2472

@@ -27,33 +75,251 @@ pub fn run(interactive: &bool) -> Result<(), Box<dyn std::error::Error>> {
2775
Ok(())
2876
}
2977

30-
fn interactive_init() -> inquire::error::InquireResult<()> {
31-
let flag_regex;
32-
// let registry_domain;
33-
// let registry_build_user;
34-
// let registry_build_pass;
35-
// let registry_cluster_user;
36-
// let registry_cluster_pass;
37-
// let defaults_difficulty;
38-
// let defaults_resources_cpu;
39-
// let defaults_resources_mem;
40-
//let points_difficulty
41-
//let deploy_profile
42-
// contd
43-
44-
//println!() about you can press enter to leave blank and all of these
45-
// can be set via environment variables also
46-
47-
flag_regex = inquire::Text::new("Regex of flags:")
78+
fn interactive_init() -> inquire::error::InquireResult<InitVars> {
79+
let mut points_difficulty = Vec::new();
80+
let mut deploy_profiles = Vec::new();
81+
82+
println!("For all prompts below, simply press Enter to leave blank.");
83+
println!("All fields that can be set in rcds.yaml can also be set via environment variables.");
84+
85+
let flag_regex = inquire::Text::new("Flag regex:")
4886
.with_help_message(
4987
"This regex will be used to validate the individual flags of your challenges later.",
50-
)
88+
) // TODO: also provide regex examples for help
5189
.prompt()?; // yo is this even a good idea to have the user provide the regex
52-
println!("{flag_regex}");
53-
Ok(())
90+
// TODO: with placeholder?
91+
92+
let registry_domain = inquire::Text::new ("Container registry:")
93+
.with_help_message("This is the domain of your remote container registry, which includes both the endpoint details and your repository name.") //where you will push images to and where your cluster will pull challenge images from.")
94+
.prompt()?;
95+
96+
let registry_build_user = inquire::Text::new ("Container registry user (YOURS):")
97+
.with_help_message("Your username to the remote container registry, which you will use to push containers to.")
98+
.prompt()?;
99+
100+
// TODO: do we actually want to be in charge of these credentials vs letting the container building utility take care of it?
101+
let registry_build_pass = inquire::Password::new("Container registry password (YOURS):")
102+
.with_help_message("Your password to the remote container registry, which you will use to push containers to.") // TODO: could this support username:pat too?
103+
.with_display_mode(inquire::PasswordDisplayMode::Masked)
104+
.with_custom_confirmation_message("Enter again:")
105+
.prompt()?;
106+
107+
let registry_cluster_user = inquire::Text::new ("Container registry user (CLUSTER'S):")
108+
.with_help_message("The cluster's username to the remote container registry, which it will use to pull containers from.")
109+
.prompt()?;
110+
111+
// TODO: would the cluster not use a token of some sort?
112+
let registry_cluster_pass = inquire::Password::new("Container registry password (CLUSTER'S):")
113+
.with_help_message("The cluster's password to the remote container registry, which it will use to pull containers from.")
114+
.prompt()?;
115+
116+
println!("You can define several challenge difficulty classes below:");
117+
loop {
118+
// TODO: theres no reason these need to be numbers instead of open strings, e.g. for "easy"
119+
let difficulty_class_rank = inquire::CustomType::<u64>::new("Difficulty rank:")
120+
// default parser calls std::u64::from_str
121+
.with_error_message("Please type a valid number.")
122+
.with_help_message("The rank of the difficulty class as an unsigned integer, with lower numbers being \"easier.\"")
123+
.prompt()?;
124+
125+
// TODO: support static-point challenges
126+
let difficulty_class_min = inquire::CustomType::<u64>::new("Minimum number of points:")
127+
// default parser calls std::u64::from_str
128+
.with_error_message("Please type a valid number.")
129+
.with_help_message("Challenge points are dynamic: the maximum number of points that challenges within this difficulty class are worth.")
130+
.prompt()?;
131+
132+
let difficulty_class_max = inquire::CustomType::<u64>::new("Maximum number of points:")
133+
// default parser calls std::u64::from_str
134+
.with_error_message("Please type a valid number.")
135+
.with_help_message("Challenge points are dynamic: the minimum number of points that challenges within this difficulty class are worth.")
136+
.prompt()?;
137+
138+
let points_object = Points {
139+
difficulty: difficulty_class_rank.to_string(),
140+
min: difficulty_class_min.to_string(),
141+
max: difficulty_class_max.to_string(),
142+
};
143+
points_difficulty.push(points_object);
144+
145+
let again = inquire::Confirm::new("Do you want to provide another difficulty class?")
146+
.with_default(false)
147+
.prompt()?;
148+
if !again {
149+
break;
150+
}
151+
}
152+
153+
let defaults_difficulty = inquire::Select::new(
154+
"Please choose the default difficulty class:",
155+
points_difficulty.clone(),
156+
)
157+
.prompt()?;
158+
159+
// TODO: how much format validation should these two do now vs offloading to validate() later? current inquire replacement calls are temporary and do the zero checking, just grabbing a String
160+
// defaults_resources_cpu = inquire::CustomType::<u64>::new("Default CPUs per challenge:")
161+
// // default parser calls std::u64::from_str
162+
// .with_error_message("Please type a valid number.")
163+
// .with_help_message("The maximum limit of CPU resources per instance of challenge deployment (\"pod\").")
164+
// .prompt()?;
165+
let defaults_resources_cpu = inquire::Text::new("Default limit of CPUs per challenge")
166+
.with_help_message(
167+
"The maximum limit of CPU resources per instance of challenge deployment (\"pod\").",
168+
)
169+
.prompt()?;
170+
171+
// defaults_resources_memory = inquire::CustomType::<String>::new("")
172+
// .with_parser(&|i|
173+
// {
174+
// let re = Regex::new(r"^[0-9]+$") // TODO
175+
// })
176+
let defaults_resources_memory = inquire::Text::new("Default limit of memory per challenge")
177+
.with_help_message(
178+
"The maximum limit of memory resources per instance of challenge deployment (\"pod\").",
179+
)
180+
.prompt()?;
181+
182+
println!("You can define several challenge difficulty classes below.");
183+
loop {
184+
let name = inquire::Text::new("Profile name:")
185+
.with_help_message("The name of the deployment profile. One profile named \"default\" is recommended. You can add additional profiles.")
186+
.prompt()?;
187+
let frontend_url = inquire::Text::new("Frontend URL:")
188+
.with_help_message("The URL of the RNG scoreboard.") // TODO: can definitely say more about why this is significant
189+
.prompt()?;
190+
191+
let frontend_token = inquire::Text::new("Frontend token:")
192+
.with_help_message("The token for RNG to authenticate itself into the scoreboard.") // TODO: again, say more
193+
.prompt()?;
194+
195+
let challenges_domain = inquire::Text::new("Challenges domain:")
196+
.with_help_message("Domain that challenges are hosted under.")
197+
.prompt()?;
198+
199+
let kubecontext = inquire::Text::new("Kube context:")
200+
.with_help_message(
201+
"The name of the context that kubectl looks for to interface with the cluster.",
202+
)
203+
.prompt()?;
204+
205+
let s3_endpoint = inquire::Text::new("S3 endpoint:")
206+
.with_help_message("Challenge artifacts and static files will be hosted on and served from S3. The endpoint of the S3 bucket server.")
207+
.prompt()?;
208+
209+
let s3_region = inquire::Text::new("S3 region:")
210+
.with_help_message("The region that the S3 bucket is hosted.")
211+
.prompt()?;
212+
213+
let s3_accesskey = inquire::Text::new("S3 access key:")
214+
.with_help_message("The public access key to the S3 bucket.")
215+
.prompt()?;
216+
217+
let s3_secretkey = inquire::Text::new("S3 secret key:")
218+
.with_help_message("The secret acess key to the S3 bucket.")
219+
.prompt()?;
220+
221+
let profile_object = Profile {
222+
profile_name: name,
223+
frontend_url,
224+
frontend_token,
225+
challenges_domain,
226+
kubecontext,
227+
s3_endpoint,
228+
s3_region,
229+
s3_accesskey,
230+
s3_secretaccesskey: s3_secretkey,
231+
};
232+
deploy_profiles.push(profile_object);
233+
234+
let again = inquire::Confirm::new("Do you want to provide another deployment profile?")
235+
.with_default(false)
236+
.prompt()?;
237+
if !again {
238+
break;
239+
}
240+
}
241+
242+
// Put everything into the struct and return it
243+
let options = InitVars {
244+
flag_regex,
245+
registry_domain,
246+
registry_build_user,
247+
registry_build_pass,
248+
registry_cluster_user,
249+
registry_cluster_pass,
250+
defaults_difficulty: defaults_difficulty.difficulty,
251+
defaults_resources_cpu,
252+
defaults_resources_memory,
253+
points: points_difficulty,
254+
profiles: deploy_profiles,
255+
};
256+
257+
Ok(options)
258+
}
259+
260+
fn noninteractive_init() -> InitVars {
261+
InitVars {
262+
flag_regex: String::from("ctf{.*}"), // TODO: do that wildcard in most common regex flavor since Rust regex supports multiple styles
263+
registry_domain: String::from("ghcr.io/youraccount"),
264+
registry_build_user: String::from("admin"),
265+
registry_build_pass: String::from("notrealcreds"),
266+
registry_cluster_user: String::from("cluster_user"),
267+
registry_cluster_pass: String::from("alsofake"),
268+
defaults_difficulty: String::from("1"),
269+
defaults_resources_cpu: String::from("1"),
270+
defaults_resources_memory: String::from("500M"), //(500, Some(String::from("M"))),
271+
points: vec![
272+
Points {
273+
difficulty: String::from("1"),
274+
min: String::from("69"),
275+
max: String::from("420"),
276+
},
277+
Points {
278+
difficulty: String::from("2"),
279+
min: String::from("200"),
280+
max: String::from("500"),
281+
},
282+
],
283+
profiles: vec![Profile {
284+
profile_name: String::from("default"),
285+
frontend_url: String::from("https://ctf.coolguy.xyz"),
286+
frontend_token: String::from("secretsecretsecret"),
287+
challenges_domain: String::from("chals.coolguy.xyz"),
288+
kubecontext: String::from("ctf-cluster"),
289+
s3_endpoint: String::from("s3.coolguy.xyz"),
290+
s3_region: String::from("us-west-2"),
291+
s3_accesskey: String::from("accesskey"),
292+
s3_secretaccesskey: String::from("secretkey"),
293+
}],
294+
}
54295
}
55296

56-
fn noninteractive_init() //-> u64
57-
{
58-
//
297+
fn blank_init() -> InitVars {
298+
InitVars {
299+
flag_regex: String::new(),
300+
registry_domain: String::new(),
301+
registry_build_user: String::new(),
302+
registry_build_pass: String::new(),
303+
registry_cluster_user: String::new(),
304+
registry_cluster_pass: String::new(),
305+
defaults_difficulty: String::new(),
306+
defaults_resources_cpu: String::new(),
307+
defaults_resources_memory: String::new(),
308+
points: vec![Points {
309+
difficulty: String::new(),
310+
min: String::new(),
311+
max: String::new(),
312+
}],
313+
profiles: vec![Profile {
314+
profile_name: String::new(),
315+
frontend_url: String::new(),
316+
frontend_token: String::new(),
317+
challenges_domain: String::new(),
318+
kubecontext: String::new(),
319+
s3_endpoint: String::new(),
320+
s3_region: String::new(),
321+
s3_accesskey: String::new(),
322+
s3_secretaccesskey: String::new(),
323+
}],
324+
}
59325
}

src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ fn dispatch(cli: cli::Cli) -> anyhow::Result<()> {
9191
}
9292

9393
cli::Commands::Init {
94-
interactive
95-
} => commands::init::run(interactive)
94+
interactive,
95+
blank
96+
} => commands::init::run(interactive, blank)
9697
}
9798
}

0 commit comments

Comments
 (0)