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

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/data

49
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,49 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'gregory'",
"cargo": {
"args": [
"build",
"--bin=gregory",
"--package=gregory",
],
"filter": {
"name": "gregory",
"kind": "bin"
}
},
"args": [
"run",
"-c",
"gregory.example.yml"
],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'gregory'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=gregory",
"--package=gregory"
],
"filter": {
"name": "gregory",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

108
Cargo.lock generated
View file

@ -63,6 +63,18 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.23" version = "4.5.23"
@ -124,15 +136,27 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "gregory" name = "gregory"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"alphanumeric-sort", "alphanumeric-sort",
"clap", "clap",
"clap_complete", "clap_complete",
"serde", "serde",
"serde_yml", "serde_yml",
"uuid",
] ]
[[package]] [[package]]
@ -169,6 +193,12 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libyml" name = "libyml"
version = "0.0.5" version = "0.0.5"
@ -185,6 +215,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.92"
@ -203,6 +242,36 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.18"
@ -273,12 +342,28 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"rand",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@ -351,3 +436,24 @@ name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -1,7 +1,8 @@
[package] [package]
name = "gregory" name = "gregory"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
license = "GPL-3.0-only"
[dependencies] [dependencies]
alphanumeric-sort = "1.5.3" alphanumeric-sort = "1.5.3"
@ -9,6 +10,7 @@ clap = { version = "4.5.23", features = ["derive"] }
clap_complete = "4.5.40" clap_complete = "4.5.40"
serde = { version = "1.0.216", features = ["derive"] } serde = { version = "1.0.216", features = ["derive"] }
serde_yml = "0.0.12" serde_yml = "0.0.12"
uuid = { version = "1.11.0", features = ["v7", "fast-rng"] }
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

View file

@ -2,6 +2,8 @@
This is Gregory. Gregory controls repos. Gregory keeps track of updating repos, trying to be simple and elegant, but enough. This is Gregory. Gregory controls repos. Gregory keeps track of updating repos, trying to be simple and elegant, but enough.
## THIS IS A PROTOTYPE
## Documentation ## Documentation
Go look at [`docs/`](/docs/) Go look at [`docs/`](/docs/)

View file

@ -0,0 +1,3 @@
# How commands are run
I was unable to find a way to directly run *multiple* commands via Docker/Podman. Instead of doing that, greg puts all the commands in a temporary script, mounts it inside, and then run it with

View file

@ -14,6 +14,8 @@ Note: This primarily uses LibreWolf and Fedora as examples of packages and distr
- Default is CPU's threads - 2 - Default is CPU's threads - 2
- `max-jobs` (integer): The maximum number of jobs to be run at once - `max-jobs` (integer): The maximum number of jobs to be run at once
- Default is 1 - Default is 1
- `data-dir` (string): The path to put data for job logs and stuff
- **Temporary**, will be removed once SQL database support is added
**Multithreading notes (IMPORTANT)**: Gregory will first run compilation jobs, then packaging jobs for whatever programs are done, then run the `update-repo` for whichever distros are finished. For this reason, the distro names listed under `packaging` and `update-repo` *must* match. **Multithreading notes (IMPORTANT)**: Gregory will first run compilation jobs, then packaging jobs for whatever programs are done, then run the `update-repo` for whichever distros are finished. For this reason, the distro names listed under `packaging` and `update-repo` *must* match.
@ -21,16 +23,23 @@ Note: This primarily uses LibreWolf and Fedora as examples of packages and distr
## Job config ## Job config
- `id` (string): An ID to identify the job, such as the compilation of a program **(highly recommended)**
- Default is `-1` for unassigned
- If you just want to run stuff, you don't need this, but it's *highly* recommended as it allows you to filter your logs.
- `revision` (string): A revision id for the job, such as a version number for a compilation script
- Default is `1`
- `threads` (integer): The maximum number of vCPUs/threads to dedicate to a job; this can be a fractional number - `threads` (integer): The maximum number of vCPUs/threads to dedicate to a job; this can be a fractional number
- Set this as less than or equal to the max number of threads the thing you're running will use - Set this as less than or equal to the max number of threads the thing you're running will use
- See `--cpus` in the [`podman run` docs](https://docs.podman.io/en/latest/markdown/podman-run.1.html#cpus) - See `--cpus` in the [`podman run` docs](https://docs.podman.io/en/latest/markdown/podman-run.1.html#cpus)
- *Root may be required for this argument* - *Root may be required for this argument*
- If not specified, it will fall back to `max-threads` - If not specified, it will fall back to `max-threads`
- `image` (string): The Docker image to run the job in *(required)* - `image` (string): The Docker image to run the job in **(required)**
- `commands` (sequence): The commands to run *(required)* - `commands` (sequence): The commands to run **(required)**
- TODO: Add command file/bash script instead - TODO: Add command file/bash script instead
- `volumes` (sequence): Names of volumes as defined in [`volumes` (top level)](#volumes) - `volumes` (sequence): Names of volumes as defined in [`volumes` (top level)](#volumes)
- `privileged` (bool): Whether the job's container should be privileged - `privileged` (bool): Whether the job's container should be privileged
- `shell` (string): The shell to run the commands in
- Default: `/bin/sh`
## Packages (`packages`) ## Packages (`packages`)

View file

@ -1,6 +1,7 @@
//! Data structs. used by gregory and stuff for handling them //! Data structs. used by gregory and stuff for handling them
use serde::Deserialize; use serde::Deserialize;
use std::time;
use std::{collections::HashMap, fs, thread}; use std::{collections::HashMap, fs, thread};
/// The config for gregory /// The config for gregory
@ -12,57 +13,88 @@ pub(crate) struct Config {
/// - 1: Warning /// - 1: Warning
/// - 2: Info /// - 2: Info
/// - 3: Debug /// - 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 #[serde(default = "log_level", rename = "log-level")]
log_level: u8, // 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 /// Maximum number of jobs to run simultaneously
#[serde(default = "max_jobs", rename = "max-jobs")] #[serde(default = "max_jobs", rename = "max-jobs")]
max_jobs: u32, pub(crate) max_jobs: u32,
/// Maximum number of threads to use /// Maximum number of threads to use
#[serde(default = "max_threads", rename = "max-threads")] #[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 /// Holds the packages, including their compilation and packaging
/// ///
/// Format: `{ "librewolf": Package { compilation, packaging } }` /// Format: `{ "librewolf": Package { compilation, packaging } }`
/// ///
/// See [`Package`] for details /// 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 /// The jobs for updating the repo, organized by distro/repo name
#[serde(rename = "update-repo")] #[serde(rename = "update-repo")]
update_repo: HashMap<String, Job>, pub(crate) update_repo: HashMap<String, Job>,
/// All volumes, organized like this: /// All volumes, organized like this:
/// ///
/// Format: `{ "librewolf": "./data/librewolf:/librewolf" }` - like Docker/Podman formatting /// Format: `{ "librewolf": "./data/librewolf:/librewolf" }` - like Docker/Podman formatting
#[serde(default = "volumes")] #[serde(default = "volumes")]
volumes: HashMap<String, String>, pub(crate) volumes: HashMap<String, String>,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub(crate) struct Job { 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 /// 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` /// 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")] #[serde(default = "job_threads")]
threads: u32, pub(crate) threads: u32,
/// The OCi image to run it in /// The OCI image to run it in
/// ///
/// For example, `docker.io/library/debian:latest` /// For example, `docker.io/library/debian:latest`
image: String, pub(crate) image: String,
/// The commands to run in the job /// The commands to run in the job
commands: Vec<String>, pub(crate) commands: Vec<String>,
volumes: Option<Vec<String>>, /// A list of all volumes given their name - see [`Config`] -> `volumes`
/// Whether the job should be privileged pub(crate) volumes: Option<Vec<String>>,
/// Whether the job W be privileged
/// ///
/// Defauolt: false /// Defauolt: false
#[serde(default = "privileged")] #[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)] #[derive(Debug, Clone, Deserialize)]
pub(crate) struct Package { pub(crate) struct Package {
/// The compilation [`Job`] - optional /// The compilation [`Job`] - optional
compilation: Option<Job>, pub(crate) compilation: Option<Job>,
/// The packaging [`Job`]s, organized by the distro/repo name /// 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 { pub(crate) fn config_from_file(filename: String) -> Config {
@ -114,3 +146,22 @@ pub(crate) fn job_threads() -> u32 {
pub(crate) fn privileged() -> bool { pub(crate) fn privileged() -> bool {
return false; 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 alphanumeric_sort::sort_str_slice;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use clap_complete::aot::{generate, Bash, Elvish, Fish, PowerShell, Zsh}; 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::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 cli;
mod data; mod data;
@ -40,9 +49,75 @@ fn main() {
fn run(config_path: String) { fn run(config_path: String) {
let config = config_from_file(config_path); 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(),
};
} }