diff --git a/' b/' new file mode 100644 index 0000000..99c487c --- /dev/null +++ b/' @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "apiVersion")] +pub enum Schema {} + diff --git a/Cargo.lock b/Cargo.lock index a11502e..7daa4b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,12 @@ dependencies = [ "url", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -155,6 +161,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.4" @@ -307,6 +323,7 @@ dependencies = [ "eyre", "octopush_core", "octopush_infra", + "tokio", "tracing", ] @@ -319,7 +336,10 @@ dependencies = [ "git2", "hex", "rand", + "serde", + "serde_yaml", "tokio", + "tracing", ] [[package]] @@ -512,6 +532,20 @@ name = "serde" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" @@ -524,6 +558,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -736,6 +783,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" + [[package]] name = "url" version = "2.3.1" diff --git a/README.md b/README.md index 10ea947..65f05dc 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ Refer to [roadmap.md](roadmap.md) ## Installation -Octopush comes in two modes. Client or Client -> Server. Octopush can stand alone as -a client, for smaller and less secure changes. However, for organisations, it -may be useful to use Octopush in server mode, which supports more features, and -has extra security built in. +Octopush comes in two modes. Client or Client -> Server. Octopush can stand +alone as a client, for smaller and less secure changes. However, for +organisations, it may be useful to use Octopush in server mode, which supports +more features, and has extra security built in. ### Client (CLI) @@ -182,8 +182,8 @@ To run the script use octopush process --path "write-a-readme" ``` -This will cause the octopush process to automatically apply the action on the repo -and open a pr. +This will cause the octopush process to automatically apply the action on the +repo and open a pr. ### Query repositories @@ -212,8 +212,8 @@ to help test locally, as well as not cause serious issues. The server configuration is pretty much the same, except the command would look like so: `octopush server process --path "write-a-readme" --apply`. Octopush will try to infer as much as possible, but it may be needed to apply some extra flags to -specify upstream repositories and such. Octopush will also help you setup keys and -such on the first run, using `octopush setup` or `octopush server setup`. +specify upstream repositories and such. Octopush will also help you setup keys +and such on the first run, using `octopush setup` or `octopush server setup`. ## Contributing diff --git a/_examples/actions/add_releaserc/octopush.yml b/_examples/actions/add_releaserc/octopush.yml index d90c930..34d455a 100644 --- a/_examples/actions/add_releaserc/octopush.yml +++ b/_examples/actions/add_releaserc/octopush.yml @@ -1,4 +1,4 @@ -apiVersion: git.front.kjuulh.io/kjuulh/octopush/blob/main/schema/v1 +apiVersion: action name: write-a-readme select: repositories: diff --git a/_examples/actions/write_a_readme/octopush.yml b/_examples/actions/write_a_readme/octopush.yml index a4bf81e..a68bf9d 100644 --- a/_examples/actions/write_a_readme/octopush.yml +++ b/_examples/actions/write_a_readme/octopush.yml @@ -1,4 +1,4 @@ -apiVersion: git.front.kjuulh.io/kjuulh/octopush/blob/main/schema/v1 +apiVersion: action name: write-a-readme select: repositories: diff --git a/crates/octopush_cli/Cargo.toml b/crates/octopush_cli/Cargo.toml index b8dc312..2bbd299 100644 --- a/crates/octopush_cli/Cargo.toml +++ b/crates/octopush_cli/Cargo.toml @@ -11,5 +11,6 @@ octopush_core = { path = "../octopush_core" } eyre = { workspace = true } tracing = { workspace = true } +tokio = { workspace = true } clap = { version = "4.0.18" } diff --git a/crates/octopush_cli/src/commands/execute.rs b/crates/octopush_cli/src/commands/execute.rs index c066840..bbb20ff 100644 --- a/crates/octopush_cli/src/commands/execute.rs +++ b/crates/octopush_cli/src/commands/execute.rs @@ -1,4 +1,7 @@ +use std::path::{self, PathBuf}; + use clap::{Arg, ArgAction, ArgMatches, Command}; +use octopush_core::schema::{self, models::Action}; use octopush_infra::service_register::ServiceRegister; pub fn execute_cmd() -> Command { @@ -16,17 +19,39 @@ pub fn execute_cmd() -> Command { } pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> { - let _action = args + let action = args .get_one::("action") .ok_or(eyre::anyhow!("--action is required"))?; let service_register = ServiceRegister::new(); - service_register - .git_provider - .clone_from_url("https://git.front.kjuulh.io/kjuulh/cuddle".to_string()) + let action_path: PathBuf = action.into(); + + let schema = service_register + .schema_parser + .parse_file(action_path.join("octopush.yml")) .await?; + match schema { + schema::models::Schema::Action { + name, + select, + actions, + } => { + tracing::debug!(name, "running action"); + + let mut repo_clones = Vec::with_capacity(select.repositories.len()); + for repo in select.repositories { + let gp = service_register.git_provider.clone(); + repo_clones.push(tokio::spawn(async move { gp.clone_from_url(repo).await })); + } + + for repo_clone in repo_clones { + let report = repo_clone.await??; + } + } + } + service_register.cleanup().await?; Ok(()) diff --git a/crates/octopush_core/Cargo.toml b/crates/octopush_core/Cargo.toml index 7b49219..4a17767 100644 --- a/crates/octopush_core/Cargo.toml +++ b/crates/octopush_core/Cargo.toml @@ -9,7 +9,10 @@ edition = "2021" async-trait = { workspace = true } eyre = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } rand = "0.8.5" hex = "0.4.3" git2 = "0.15.0" +serde = { version = "1.0.147", features = ["derive"] } +serde_yaml = "0.9.14" diff --git a/crates/octopush_core/src/git/github.rs b/crates/octopush_core/src/git/github.rs index 8d1d471..6ccc959 100644 --- a/crates/octopush_core/src/git/github.rs +++ b/crates/octopush_core/src/git/github.rs @@ -1,3 +1,5 @@ +use git2::{Cred, RemoteCallbacks}; + use crate::storage::DynStorageEngine; use super::GitProvider; @@ -15,10 +17,32 @@ impl GitHubGitProvider { #[async_trait::async_trait] impl GitProvider for GitHubGitProvider { async fn clone_from_url(&self, url: String) -> eyre::Result<()> { + tracing::debug!(url, "allocating dir"); let dir = self.storage_engine.allocate_dir().await?; - tokio::task::spawn_blocking(move || git2::Repository::clone(url.as_str(), dir.path())) - .await??; + tokio::task::spawn_blocking(move || { + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(|url, username_from_url, _allowed_types| { + tracing::debug!(username_from_url, url, "pulling key from ssh-agent"); + Cred::ssh_key_from_agent(username_from_url.unwrap()) + }); + + let mut fo = git2::FetchOptions::new(); + fo.remote_callbacks(callbacks); + + let mut builder = git2::build::RepoBuilder::new(); + builder.fetch_options(fo); + + let path = dir.path(); + + tracing::debug!( + url, + path = path.as_os_str().to_string_lossy().to_string(), + "clone git repo" + ); + builder.clone(url.as_str(), path.as_path()) + }) + .await??; Ok(()) } diff --git a/crates/octopush_core/src/lib.rs b/crates/octopush_core/src/lib.rs index 47b6a54..3dd45ad 100644 --- a/crates/octopush_core/src/lib.rs +++ b/crates/octopush_core/src/lib.rs @@ -1,2 +1,3 @@ pub mod git; pub mod storage; +pub mod schema; diff --git a/crates/octopush_core/src/schema/mod.rs b/crates/octopush_core/src/schema/mod.rs new file mode 100644 index 0000000..3031dbb --- /dev/null +++ b/crates/octopush_core/src/schema/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod parser; diff --git a/crates/octopush_core/src/schema/models.rs b/crates/octopush_core/src/schema/models.rs new file mode 100644 index 0000000..015168c --- /dev/null +++ b/crates/octopush_core/src/schema/models.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +pub type Repository = String; + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +pub struct SelectAction { + pub repositories: Vec, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[serde(tag = "type")] +pub enum Action { + #[serde(rename = "go")] + Go { entry: String }, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(tag = "apiVersion")] +pub enum Schema { + #[serde(rename = "action")] + Action { + name: String, + select: SelectAction, + actions: Vec, + }, +} diff --git a/crates/octopush_core/src/schema/parser.rs b/crates/octopush_core/src/schema/parser.rs new file mode 100644 index 0000000..c87b23b --- /dev/null +++ b/crates/octopush_core/src/schema/parser.rs @@ -0,0 +1,69 @@ +use std::{path::PathBuf, sync::Arc}; + +use async_trait::async_trait; + +use super::models::Schema; + +#[async_trait] +pub trait SchemaParser { + async fn parse_file(&self, file: PathBuf) -> eyre::Result; +} + +pub type DynSchemaParser = Arc; + +#[derive(Debug)] +pub struct DefaultSchemaParser {} + +#[async_trait] +impl SchemaParser for DefaultSchemaParser { + async fn parse_file(&self, file: PathBuf) -> eyre::Result { + let file = tokio::fs::read(file).await?; + + self.parse(file) + } +} + +impl DefaultSchemaParser { + pub fn new() -> Self { + Self {} + } + + pub fn parse(&self, contents: Vec) -> eyre::Result { + let schema = serde_yaml::from_slice(contents.as_slice())?; + + Ok(schema) + } +} + +mod test { + use super::DefaultSchemaParser; + use crate::schema::models::{Action, Schema, SelectAction}; + + #[test] + fn can_parse_action() { + let content = r#"apiVersion: action +name: write-a-readme +select: + repositories: + - git@git.front.kjuulh.io:kjuulh/octopush-test.git +actions: + - type: go + entry: "main.go" +"#; + + let res = DefaultSchemaParser::new().parse(content.trim().into()); + + assert_eq!( + res.unwrap(), + Schema::Action { + name: "write-a-readme".into(), + select: SelectAction { + repositories: vec!["git@git.front.kjuulh.io:kjuulh/octopush-test.git".into()] + }, + actions: vec![Action::Go { + entry: "main.go".into() + }] + } + ) + } +} diff --git a/crates/octopush_core/src/storage/mod.rs b/crates/octopush_core/src/storage/mod.rs index a7f1520..38bb200 100644 --- a/crates/octopush_core/src/storage/mod.rs +++ b/crates/octopush_core/src/storage/mod.rs @@ -12,6 +12,7 @@ pub trait StorageEngine { pub type DynStorageEngine = Arc; +#[derive(Clone, Debug)] pub struct TemporaryDir { path: PathBuf, } diff --git a/crates/octopush_infra/src/service_register.rs b/crates/octopush_infra/src/service_register.rs index 6527b6b..32f338b 100644 --- a/crates/octopush_infra/src/service_register.rs +++ b/crates/octopush_infra/src/service_register.rs @@ -2,22 +2,26 @@ use std::sync::Arc; use octopush_core::{ git::{github::GitHubGitProvider, DynGitProvider}, + schema::parser::{DefaultSchemaParser, DynSchemaParser}, storage::{local::LocalStorageEngine, DynStorageEngine}, }; pub struct ServiceRegister { pub storage_engine: DynStorageEngine, pub git_provider: DynGitProvider, + pub schema_parser: DynSchemaParser, } impl ServiceRegister { pub fn new() -> Self { let storage_engine = Arc::new(LocalStorageEngine::new("/tmp/octopush".into())); let git_provider = Arc::new(GitHubGitProvider::new(storage_engine.clone())); + let schema_parser = Arc::new(DefaultSchemaParser::new()); Self { storage_engine, git_provider, + schema_parser, } }