functional prototype

update docs and structs, add actual logic, now it actually works
This commit is contained in:
askiiart 2024-12-29 16:31:56 -06:00
parent acf4c9e84d
commit df0cf3b93d
Signed by untrusted user who does not match committer: askiiart
GPG key ID: EA85979611654C30
10 changed files with 322 additions and 24 deletions

View file

@ -1,6 +1,7 @@
//! Data structs. used by gregory and stuff for handling them
use serde::Deserialize;
use std::time;
use std::{collections::HashMap, fs, thread};
/// The config for gregory
@ -12,57 +13,88 @@ pub(crate) struct Config {
/// - 1: Warning
/// - 2: Info
/// - 3: Debug
#[serde(default = "log_level", rename = "log-level")] // the rename lets it use `log-level` instead in the yaml file - this is not an alias, `log_level` in the yaml will *not* work
log_level: u8,
#[serde(default = "log_level", rename = "log-level")]
// the rename lets it use `log-level` instead in the yaml file - this is not an alias, `log_level` in the yaml will *not* work
pub(crate) log_level: u8,
/// Maximum number of jobs to run simultaneously
#[serde(default = "max_jobs", rename = "max-jobs")]
max_jobs: u32,
pub(crate) max_jobs: u32,
/// Maximum number of threads to use
#[serde(default = "max_threads", rename = "max-threads")]
max_threads: u32,
pub(crate) max_threads: u32,
#[serde(default = "data", rename = "data-dir")]
pub(crate) data_dir: String,
/// Holds the packages, including their compilation and packaging
///
/// Format: `{ "librewolf": Package { compilation, packaging } }`
///
/// See [`Package`] for details
packages: HashMap<String, Package>,
pub(crate) packages: HashMap<String, Package>,
/// The jobs for updating the repo, organized by distro/repo name
#[serde(rename = "update-repo")]
update_repo: HashMap<String, Job>,
pub(crate) update_repo: HashMap<String, Job>,
/// All volumes, organized like this:
///
/// Format: `{ "librewolf": "./data/librewolf:/librewolf" }` - like Docker/Podman formatting
#[serde(default = "volumes")]
volumes: HashMap<String, String>,
pub(crate) volumes: HashMap<String, String>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Job {
/// An ID to identify the job, such as the compilation of a program
#[serde(default = "id")]
pub(crate) id: String,
#[serde(default = "revision")]
pub(crate) revision: String,
/// How many threads to limit this job to; recommended to set it to the max threads the job will use
///
/// If `threads` isn't specified, it will fall back to `max_threads` (from [`Config`]); the same behavior applies if `threads` is greater than `max_threads`
#[serde(default = "job_threads")]
threads: u32,
/// The OCi image to run it in
pub(crate) threads: u32,
/// The OCI image to run it in
///
/// For example, `docker.io/library/debian:latest`
image: String,
pub(crate) image: String,
/// The commands to run in the job
commands: Vec<String>,
volumes: Option<Vec<String>>,
/// Whether the job should be privileged
pub(crate) commands: Vec<String>,
/// A list of all volumes given their name - see [`Config`] -> `volumes`
pub(crate) volumes: Option<Vec<String>>,
/// Whether the job W be privileged
///
/// Defauolt: false
#[serde(default = "privileged")]
privileged: bool,
pub(crate) privileged: bool,
#[serde(default = "shell")]
pub(crate) shell: String,
}
/// Holds the data for a certain package's config
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Package {
/// The compilation [`Job`] - optional
compilation: Option<Job>,
pub(crate) compilation: Option<Job>,
/// The packaging [`Job`]s, organized by the distro/repo name
packaging: HashMap<String, Job>,
pub(crate) packaging: HashMap<String, Job>,
}
/// The exit status and stuff for a [`Job`]
#[derive(Debug, Clone)]
pub(crate) struct JobExitStatus {
pub(crate) job: Job,
/// The [`Job`] this status is from
///
/// This is stored as a u16 rather than a u8 so that 65535 can be returned if there is no exit code rather than doing an Option or something, which I fear will probably come back to haunt me, but whatever
pub(crate) exit_code: u16,
/// Where the log is
///
/// TEMPORARY
/// TODO: Have main() handle logs and writing them to the database, not doing it in run_job()
pub(crate) log_path: String,
/// How long it took to run the job
pub(crate) duration: time::Duration,
/// The name of the container this job ran in
pub(crate) container_name: String,
}
pub(crate) fn config_from_file(filename: String) -> Config {
@ -114,3 +146,22 @@ pub(crate) fn job_threads() -> u32 {
pub(crate) fn privileged() -> bool {
return false;
}
/// Default (`/bin/sh`) for which shell to use
pub(crate) fn shell() -> String {
return "/bin/sh".to_string();
}
/// Default id (`-1`)
pub(crate) fn id() -> String {
return "-1".to_string();
}
/// Default revision (`1`)
pub(crate) fn revision() -> String {
return "1".to_string();
}
pub(crate) fn data() -> String {
return "./data".to_string();
}

View file

@ -3,7 +3,16 @@ use crate::data::*;
use alphanumeric_sort::sort_str_slice;
use clap::{CommandFactory, Parser};
use clap_complete::aot::{generate, Bash, Elvish, Fish, PowerShell, Zsh};
use std::fs;
use std::fs::create_dir_all;
use std::fs::write;
use std::fs::File;
use std::io::stdout;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
use uuid::Uuid;
mod cli;
mod data;
@ -40,9 +49,75 @@ fn main() {
fn run(config_path: String) {
let config = config_from_file(config_path);
println!("{:?}", config);
println!("{:#?}", config);
let mut jobs: Vec<Job> = Vec::new();
for (_, package) in config.clone().packages {
match package.compilation {
Some(tmp) => {
jobs.push(tmp);
}
None => {}
}
for (_, job) in package.packaging {
jobs.push(job);
}
}
for (_, job) in config.clone().update_repo {
jobs.push(job);
}
for job in jobs {
println!("{:#?}", run_job(config.clone(), job));
}
}
fn run_job(max_threads: u32, job: Job) {
}
fn run_job(conf: Config, job: Job) -> JobExitStatus {
// limit threads to max_threads in the config
let mut threads: u32 = job.threads;
if job.threads > conf.max_threads {
threads = conf.max_threads;
}
let container_name: String = format!("gregory-{}-{}-{}", job.id, job.revision, Uuid::now_v7());
let log_path = &format!("{}/logs/{container_name}.txt", conf.data_dir); // can't select fields in the format!() {} thing, have to do this
let log_dir: &Path = Path::new(log_path);
create_dir_all(log_dir.parent().unwrap()).unwrap();
write(log_path, job.commands.join("\n")).unwrap();
// set permissions - *unix specific*
let mut perms = File::open(log_path)
.unwrap()
.metadata()
.unwrap()
.permissions();
PermissionsExt::set_mode(&mut perms, 0o755);
let now = Instant::now();
let cmd_args: Vec<String> = vec![
"run".to_string(),
format!("--name={container_name}"),
format!("--cpus={threads}"),
format!("--privileged={}", job.privileged),
format!("-v={log_path}:/gregory-entrypoint.sh"),
format!("--entrypoint=['{}', '/gregory-entrypoint.sh']", &job.shell),
job.clone().image
];
let cmd_output = Command::new("podman").args(cmd_args).output().unwrap();
let elapsed = now.elapsed();
println!("{:?}", cmd_output);
return JobExitStatus {
container_name: container_name,
duration: elapsed,
job: job,
exit_code: cmd_output.status.code().ok_or_else(|| 65535).unwrap() as u16,
log_path: log_path.clone(),
};
}