kjuulh 85cc1d46db
feat: make sure dir is there as well
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-01-28 16:42:34 +01:00

367 lines
12 KiB
Rust

use std::{collections::HashMap, path::PathBuf};
use anyhow::Context;
use clap::ArgMatches;
use rlua::Lua;
use rlua_searcher::AddSearcher;
use crate::{
actions::shell::ShellAction,
model::{CuddleScript, CuddleShellScriptArg, CuddleVariable},
};
pub mod shell;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CuddleAction {
pub script: CuddleScript,
pub path: PathBuf,
pub description: Option<String>,
pub name: String,
}
#[allow(dead_code)]
impl CuddleAction {
pub fn new(
script: CuddleScript,
path: PathBuf,
name: String,
description: Option<String>,
) -> Self {
Self {
script,
path,
name,
description,
}
}
pub fn execute(
self,
matches: &ArgMatches,
variables: Vec<CuddleVariable>,
) -> anyhow::Result<()> {
match self.script {
CuddleScript::Shell(s) => {
let mut arg_variables: Vec<CuddleVariable> = vec![];
if let Some(args) = s.args {
for (k, v) in args {
let var = match v {
CuddleShellScriptArg::Env(e) => {
let env_var = matches.get_one::<String>(&k).cloned().ok_or(
anyhow::anyhow!(
"failed to find env variable with key: {}",
&e.key
),
)?;
CuddleVariable::new(k.clone(), env_var)
}
CuddleShellScriptArg::Flag(flag) => {
match matches.get_one::<String>(&flag.name) {
Some(flag_var) => {
CuddleVariable::new(k.clone(), flag_var.clone())
}
None => continue,
}
}
};
arg_variables.push(var);
}
} else {
arg_variables = vec![]
};
let mut vars = variables.clone();
vars.append(&mut arg_variables);
log::trace!("preparing to run action");
match ShellAction::new(
self.name.clone(),
format!(
"{}/scripts/{}.sh",
self.path
.to_str()
.expect("action doesn't have a name, this should never happen"),
self.name
),
)
.execute(vars)
{
Ok(()) => {
log::trace!("finished running action");
Ok(())
}
Err(e) => {
log::error!("{}", e);
Err(e)
}
}
}
CuddleScript::Dagger(_d) => Err(anyhow::anyhow!("not implemented yet!")),
CuddleScript::Lua(l) => {
let lua = Lua::new();
let mut map = HashMap::new();
//map.insert("init".into(), "print(\"something\")".into());
let lua_dir = PathBuf::new().join(&self.path).join("scripts").join("lua");
if lua_dir.exists() {
let absolute_lua_dir = lua_dir.canonicalize()?;
for entry in walkdir::WalkDir::new(&lua_dir)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.metadata()?.is_file() {
let full_file_path = entry.path().canonicalize()?;
let relative_module_path =
full_file_path.strip_prefix(&absolute_lua_dir)?;
let module_path = relative_module_path
.to_string_lossy()
.to_string()
.trim_end_matches("/init.lua")
.trim_end_matches(".lua")
.replace("/", ".");
let contents = std::fs::read_to_string(entry.path())?;
tracing::trace!(module_path = &module_path, "adding lua file");
map.insert(module_path.into(), contents.into());
}
}
}
let lua_rocks_dir = PathBuf::new()
.join(&self.path)
.join("lua_modules")
.join("share")
.join("lua")
.join("5.4");
if lua_rocks_dir.exists() {
let absolute_lua_dir = lua_rocks_dir.canonicalize()?;
for entry in walkdir::WalkDir::new(&lua_rocks_dir)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.metadata()?.is_file() {
let full_file_path = entry.path().canonicalize()?;
let relative_module_path =
full_file_path.strip_prefix(&absolute_lua_dir)?;
let module_path = relative_module_path
.to_string_lossy()
.to_string()
.trim_end_matches("/init.lua")
.trim_end_matches(".lua")
.replace("/", ".");
let contents = std::fs::read_to_string(entry.path())?;
tracing::trace!(module_path = &module_path, "adding lua file");
map.insert(module_path.into(), contents.into());
}
}
}
lua.context::<_, anyhow::Result<()>>(|lua_ctx| {
lua_ctx.add_searcher(map)?;
let globals = lua_ctx.globals();
let lua_script_entry = std::fs::read_to_string(
PathBuf::new()
.join(&self.path)
.join("scripts")
.join(format!("{}.lua", &self.name)),
)
.context("failed to find lua script")?;
lua_ctx
.load(&lua_script_entry)
.set_name(&self.name)?
.exec()?;
Ok(())
})?;
Ok(())
}
CuddleScript::Rust(script) => Ok(()),
}
}
}
pub mod rust_action {
use std::{path::PathBuf, time::Duration};
use anyhow::Context;
use futures_util::StreamExt;
use reqwest::Method;
use tokio::{fs::File, io::AsyncWriteExt};
use crate::model::{CuddleRustScript, CuddleRustUpstream, CuddleVariable};
pub struct RustActionConfig {
pub config_dir: PathBuf,
pub cache_dir: PathBuf,
}
impl Default for RustActionConfig {
fn default() -> Self {
let config = dirs::config_dir().expect("to be able to find a valid .config dir");
let cache = dirs::cache_dir().expect("to be able to find a valid .cache dir");
Self {
config_dir: config,
cache_dir: cache,
}
}
}
pub struct RustAction {
pub config: RustActionConfig,
pub plan: String,
pub binary_name: String,
}
impl RustAction {
pub fn new(plan: String, binary_name: String) -> Self {
Self {
plan,
binary_name,
config: RustActionConfig::default(),
}
}
pub async fn execute(
&self,
script: CuddleRustScript,
variables: impl IntoIterator<Item = CuddleVariable>,
) -> anyhow::Result<()> {
let commit_sha = self
.get_commit_sha()
.await
.context("failed to find a valid commit sha on the inferred path: .cuddle/plan")?;
let binary_hash = self.calculate_hash(commit_sha)?;
// Get cached binary
// let binary = match self.get_binary(&binary_hash).await? {
// Some(binary) => binary,
// None => self.fetch_binary(&script, &binary_hash).await?,
// };
// Execute binary
Ok(())
}
async fn get_binary(
&self,
binary_hash: impl Into<String>,
) -> anyhow::Result<Option<RustBinary>> {
let binary_path = self.get_cached_binary_path(binary_hash);
if !binary_path.exists() {
return Ok(None);
}
Ok(Some(RustBinary {}))
}
fn get_cached_binary_path(&self, binary_hash: impl Into<String>) -> PathBuf {
let cached_binary_name = self.get_cached_binary_name(binary_hash);
let binary_path = self
.config
.cache_dir
.join("binaries")
.join(cached_binary_name);
binary_path
}
#[inline]
fn get_cached_binary_name(&self, binary_hash: impl Into<String>) -> String {
format!("{}-{}", binary_hash.into(), self.binary_name)
}
async fn get_commit_sha(&self) -> anyhow::Result<String> {
let repo = git2::Repository::open(".cuddle/plan")?;
let head = repo.head()?;
let commit = head.peel_to_commit()?;
let commit_sha = commit.id();
Ok(commit_sha.to_string())
}
// async fn fetch_binary(
// &self,
// script: &CuddleRustScript,
// binary_hash: impl Into<String>,
// ) -> anyhow::Result<RustBinary> {
//let upstream = &script.upstream;
//TODO: we should interpret some template variables in the upstream string. Ignore for now though
// match UpstreamRustBinary::from(upstream) {
// UpstreamRustBinary::HttpBased { url } => {
// let client = reqwest::ClientBuilder::new()
// .user_agent(concat!(
// env!("CARGO_PKG_NAME"),
// "/",
// env!("CARGO_PKG_VERSION")
// ))
// .connect_timeout(Duration::from_secs(5))
// .build()?;
// let resp = client.request(Method::GET, url).send().await?;
// let mut stream = resp.bytes_stream();
// let mut file = File::create(self.get_cached_binary_name(binary_hash)).await?;
// while let Some(item) = stream.next().await {
// let chunk = item?;
// file.write_all(&chunk).await?;
// }
// // Make sure the entire file is written before we execute it
// file.flush().await?;
// todo!()
// }
// }
// }
fn calculate_hash(&self, commit_sha: impl Into<Vec<u8>>) -> anyhow::Result<String> {
let mut contents: Vec<u8> = Vec::new();
contents.append(&mut self.plan.clone().into_bytes());
contents.append(&mut commit_sha.into());
let hash = blake3::hash(&contents);
let hex = hash.to_hex();
Ok(hex.to_string())
}
}
pub struct RustBinary {}
pub enum UpstreamRustBinary {
HttpBased { url: String },
}
impl From<CuddleRustUpstream> for UpstreamRustBinary {
fn from(value: CuddleRustUpstream) -> Self {
match value {
CuddleRustUpstream::Gitea { url } => Self::HttpBased { url },
}
}
}
impl From<&CuddleRustUpstream> for UpstreamRustBinary {
fn from(value: &CuddleRustUpstream) -> Self {
match value {
CuddleRustUpstream::Gitea { url } => Self::HttpBased { url: url.clone() },
}
}
}
}