Rewrite rust #38

Merged
kjuulh merged 24 commits from experiment/local into v0.3 2022-11-27 12:21:36 +01:00
15 changed files with 231 additions and 16 deletions
Showing only changes of commit 1879ad00ee - Show all commits

6
' Normal file
View File

@ -0,0 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "apiVersion")]
pub enum Schema {}

53
Cargo.lock generated
View File

@ -124,6 +124,12 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -155,6 +161,16 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 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]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.4" version = "1.0.4"
@ -307,6 +323,7 @@ dependencies = [
"eyre", "eyre",
"octopush_core", "octopush_core",
"octopush_infra", "octopush_infra",
"tokio",
"tracing", "tracing",
] ]
@ -319,7 +336,10 @@ dependencies = [
"git2", "git2",
"hex", "hex",
"rand", "rand",
"serde",
"serde_yaml",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@ -512,6 +532,20 @@ name = "serde"
version = "1.0.147" version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 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]] [[package]]
name = "serde_json" name = "serde_json"
@ -524,6 +558,19 @@ dependencies = [
"serde", "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]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@ -736,6 +783,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unsafe-libyaml"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
[[package]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.3.1"

View File

@ -34,10 +34,10 @@ Refer to [roadmap.md](roadmap.md)
## Installation ## Installation
Octopush comes in two modes. Client or Client -> Server. Octopush can stand alone as Octopush comes in two modes. Client or Client -> Server. Octopush can stand
a client, for smaller and less secure changes. However, for organisations, it alone as a client, for smaller and less secure changes. However, for
may be useful to use Octopush in server mode, which supports more features, and organisations, it may be useful to use Octopush in server mode, which supports
has extra security built in. more features, and has extra security built in.
### Client (CLI) ### Client (CLI)
@ -182,8 +182,8 @@ To run the script use
octopush process --path "write-a-readme" octopush process --path "write-a-readme"
``` ```
This will cause the octopush process to automatically apply the action on the repo This will cause the octopush process to automatically apply the action on the
and open a pr. repo and open a pr.
### Query repositories ### 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: 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 `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 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 specify upstream repositories and such. Octopush will also help you setup keys
such on the first run, using `octopush setup` or `octopush server setup`. and such on the first run, using `octopush setup` or `octopush server setup`.
## Contributing ## Contributing

View File

@ -1,4 +1,4 @@
apiVersion: git.front.kjuulh.io/kjuulh/octopush/blob/main/schema/v1 apiVersion: action
name: write-a-readme name: write-a-readme
select: select:
repositories: repositories:

View File

@ -1,4 +1,4 @@
apiVersion: git.front.kjuulh.io/kjuulh/octopush/blob/main/schema/v1 apiVersion: action
name: write-a-readme name: write-a-readme
select: select:
repositories: repositories:

View File

@ -11,5 +11,6 @@ octopush_core = { path = "../octopush_core" }
eyre = { workspace = true } eyre = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tokio = { workspace = true }
clap = { version = "4.0.18" } clap = { version = "4.0.18" }

View File

@ -1,4 +1,7 @@
use std::path::{self, PathBuf};
use clap::{Arg, ArgAction, ArgMatches, Command}; use clap::{Arg, ArgAction, ArgMatches, Command};
use octopush_core::schema::{self, models::Action};
use octopush_infra::service_register::ServiceRegister; use octopush_infra::service_register::ServiceRegister;
pub fn execute_cmd() -> Command { pub fn execute_cmd() -> Command {
@ -16,17 +19,39 @@ pub fn execute_cmd() -> Command {
} }
pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> { pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> {
let _action = args let action = args
.get_one::<String>("action") .get_one::<String>("action")
.ok_or(eyre::anyhow!("--action is required"))?; .ok_or(eyre::anyhow!("--action is required"))?;
let service_register = ServiceRegister::new(); let service_register = ServiceRegister::new();
service_register let action_path: PathBuf = action.into();
.git_provider
.clone_from_url("https://git.front.kjuulh.io/kjuulh/cuddle".to_string()) let schema = service_register
.schema_parser
.parse_file(action_path.join("octopush.yml"))
.await?; .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?; service_register.cleanup().await?;
Ok(()) Ok(())

View File

@ -9,7 +9,10 @@ edition = "2021"
async-trait = { workspace = true } async-trait = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true }
rand = "0.8.5" rand = "0.8.5"
hex = "0.4.3" hex = "0.4.3"
git2 = "0.15.0" git2 = "0.15.0"
serde = { version = "1.0.147", features = ["derive"] }
serde_yaml = "0.9.14"

View File

@ -1,3 +1,5 @@
use git2::{Cred, RemoteCallbacks};
use crate::storage::DynStorageEngine; use crate::storage::DynStorageEngine;
use super::GitProvider; use super::GitProvider;
@ -15,9 +17,31 @@ impl GitHubGitProvider {
#[async_trait::async_trait] #[async_trait::async_trait]
impl GitProvider for GitHubGitProvider { impl GitProvider for GitHubGitProvider {
async fn clone_from_url(&self, url: String) -> eyre::Result<()> { async fn clone_from_url(&self, url: String) -> eyre::Result<()> {
tracing::debug!(url, "allocating dir");
let dir = self.storage_engine.allocate_dir().await?; let dir = self.storage_engine.allocate_dir().await?;
tokio::task::spawn_blocking(move || git2::Repository::clone(url.as_str(), dir.path())) 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??; .await??;
Ok(()) Ok(())

View File

@ -1,2 +1,3 @@
pub mod git; pub mod git;
pub mod storage; pub mod storage;
pub mod schema;

View File

@ -0,0 +1,2 @@
pub mod models;
pub mod parser;

View File

@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
pub type Repository = String;
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct SelectAction {
pub repositories: Vec<Repository>,
}
#[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<Action>,
},
}

View File

@ -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<Schema>;
}
pub type DynSchemaParser = Arc<dyn SchemaParser + Send + Sync>;
#[derive(Debug)]
pub struct DefaultSchemaParser {}
#[async_trait]
impl SchemaParser for DefaultSchemaParser {
async fn parse_file(&self, file: PathBuf) -> eyre::Result<Schema> {
let file = tokio::fs::read(file).await?;
self.parse(file)
}
}
impl DefaultSchemaParser {
pub fn new() -> Self {
Self {}
}
pub fn parse(&self, contents: Vec<u8>) -> eyre::Result<Schema> {
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()
}]
}
)
}
}

View File

@ -12,6 +12,7 @@ pub trait StorageEngine {
pub type DynStorageEngine = Arc<dyn StorageEngine + Send + Sync>; pub type DynStorageEngine = Arc<dyn StorageEngine + Send + Sync>;
#[derive(Clone, Debug)]
pub struct TemporaryDir { pub struct TemporaryDir {
path: PathBuf, path: PathBuf,
} }

View File

@ -2,22 +2,26 @@ use std::sync::Arc;
use octopush_core::{ use octopush_core::{
git::{github::GitHubGitProvider, DynGitProvider}, git::{github::GitHubGitProvider, DynGitProvider},
schema::parser::{DefaultSchemaParser, DynSchemaParser},
storage::{local::LocalStorageEngine, DynStorageEngine}, storage::{local::LocalStorageEngine, DynStorageEngine},
}; };
pub struct ServiceRegister { pub struct ServiceRegister {
pub storage_engine: DynStorageEngine, pub storage_engine: DynStorageEngine,
pub git_provider: DynGitProvider, pub git_provider: DynGitProvider,
pub schema_parser: DynSchemaParser,
} }
impl ServiceRegister { impl ServiceRegister {
pub fn new() -> Self { pub fn new() -> Self {
let storage_engine = Arc::new(LocalStorageEngine::new("/tmp/octopush".into())); let storage_engine = Arc::new(LocalStorageEngine::new("/tmp/octopush".into()));
let git_provider = Arc::new(GitHubGitProvider::new(storage_engine.clone())); let git_provider = Arc::new(GitHubGitProvider::new(storage_engine.clone()));
let schema_parser = Arc::new(DefaultSchemaParser::new());
Self { Self {
storage_engine, storage_engine,
git_provider, git_provider,
schema_parser,
} }
} }