367 lines
12 KiB
Rust
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() },
|
|
}
|
|
}
|
|
}
|
|
}
|