Add github (#41)
All checks were successful
continuous-integration/drone/push Build is passing

Co-authored-by: kjuulh <contact@kjuulh.io>
Reviewed-on: #41
This commit is contained in:
2022-11-27 20:07:41 +00:00
parent a3ecad1355
commit bb51770505
13 changed files with 812 additions and 36 deletions

View File

@@ -2,7 +2,10 @@ use std::path::PathBuf;
use clap::{Arg, ArgAction, ArgMatches, Command};
use octopush_core::{
git::{git::LocalGitProviderOptions, gitea::client::DefaultGiteaClientOptions},
git::{
git::LocalGitProviderOptions, gitea::client::DefaultGiteaClientOptions,
github::github_client::DefaultGitHubClientOptions,
},
schema,
};
use octopush_infra::service_register::ServiceRegister;
@@ -41,6 +44,20 @@ pub fn execute_cmd() -> Command {
.env("GITEA_URL")
.required(false),
)
.arg(
Arg::new("github-api-token")
.long("github-api-token")
.action(ArgAction::Set)
.env("GITHUB_API_TOKEN")
.required(false),
)
.arg(
Arg::new("github-username")
.long("github-username")
.action(ArgAction::Set)
.env("GITHUB_USERNAME")
.required(false),
)
}
pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> {
@@ -52,6 +69,9 @@ pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> {
let gitea_username = args.get_one::<String>("gitea-username");
let gitea_url = args.get_one::<String>("gitea-url");
let github_http_token = args.get_one::<String>("github-api-token");
let github_username = args.get_one::<String>("github-username");
let service_register = ServiceRegister::new(
LocalGitProviderOptions { http_auth: None },
DefaultGiteaClientOptions {
@@ -61,7 +81,13 @@ pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> {
.map(|(u, ht)| format!("{}:{}", u, ht))
.map(|t| t.clone()),
},
);
DefaultGitHubClientOptions {
basicauth: github_username
.zip(github_http_token)
.map(|(u, ht)| format!("{}:{}", u, ht))
.map(|t| t.clone()),
},
)?;
let action_path: PathBuf = action.into();
@@ -91,6 +117,13 @@ pub async fn execute_subcommand(args: &ArgMatches) -> eyre::Result<()> {
.run(gitea, &action_path, &action)
.await?;
}
if let Some(github) = &select.github {
service_register
.github_selector
.run(github, &action_path, &action)
.await?;
}
}
}

View File

@@ -21,3 +21,4 @@ git2 = { version = "0.15.0", features = [
] }
serde = { version = "1.0.147", features = ["derive"] }
serde_yaml = "0.9.14"
octocrab = { version = "0.17.0", features = ["futures-core", "futures-util"] }

View File

@@ -239,9 +239,15 @@ impl GitProvider for LocalGitProvider {
remote.fetch(refspec, Some(&mut fo), None)?;
let fetch_head = repo.find_reference("FETCH_HEAD")?;
let commit = repo.reference_to_annotated_commit(&fetch_head)?;
Self::do_merge(&repo, &branch_name, commit)?;
match repo.find_reference("FETCH_HEAD") {
Ok(fetch_head) => {
let commit = repo.reference_to_annotated_commit(&fetch_head)?;
Self::do_merge(&repo, &branch_name, commit)?;
}
Err(e) => {
tracing::info!(error = e.to_string(), "upstream branch not found");
}
}
Ok(())
}

View File

@@ -0,0 +1,61 @@
use std::sync::Arc;
use async_trait::async_trait;
use octocrab::{Octocrab, OctocrabBuilder};
use super::GitHubClient;
pub struct DefaultGitHubClientOptions {
pub basicauth: Option<String>,
}
pub struct DefaultGitHubClient {
github: Arc<Octocrab>,
}
impl DefaultGitHubClient {
pub fn new(options: &DefaultGitHubClientOptions) -> eyre::Result<Self> {
let mut github = OctocrabBuilder::new();
if let Some(basicauth) = options.basicauth.clone() {
if let Some((username, password)) = basicauth.split_once(":") {
github = github.basic_auth(username.into(), password.into());
}
}
Ok(Self {
github: Arc::new(github.build()?),
})
}
}
#[async_trait]
impl GitHubClient for DefaultGitHubClient {
async fn get_clone_url(&self, owner: String, repo_name: String) -> eyre::Result<String> {
let repo = self.github.repos(&owner, &repo_name).get().await?;
let clone_url = repo
.ssh_url
.ok_or(eyre::anyhow!("clone_url is not set for repository"))?;
Ok(clone_url)
}
async fn create_pull_request(
&self,
owner: &String,
repo_name: &String,
pull_request_name: &String,
) -> eyre::Result<()> {
self.github
.pulls(owner, repo_name)
.create(
pull_request_name.clone(),
pull_request_name.to_lowercase().replace(" ", "-"),
"main",
)
.send()
.await?;
Ok(())
}
}

View File

@@ -0,0 +1,79 @@
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use git2::Repository;
use tokio::sync::Mutex;
use crate::{git::DynGitProvider, schema::models::GitPushPullRequest, storage::DynStorageEngine};
use super::{DynGitHubClient, GitHubProvider};
pub struct DefaultGitHubProvider {
git_provider: DynGitProvider,
_storage_engine: DynStorageEngine,
github_client: DynGitHubClient,
}
impl DefaultGitHubProvider {
pub fn new(
git_provider: DynGitProvider,
storage_engine: DynStorageEngine,
github_client: DynGitHubClient,
) -> Self {
Self {
git_provider,
_storage_engine: storage_engine,
github_client,
}
}
}
#[async_trait]
impl GitHubProvider for DefaultGitHubProvider {
async fn clone_from_qualified(&self, repo: &String) -> eyre::Result<(PathBuf, Repository)> {
let (owner, repo_name) = repo
.split_once("/")
.ok_or(eyre::anyhow!("repo is not a valid format"))?;
let clone_url = self
.github_client
.get_clone_url(owner.into(), repo_name.into())
.await?;
let (path, repo) = self.git_provider.clone_from_url(&clone_url).await?;
Ok((path, repo))
}
async fn create_branch(
&self,
repo: Arc<Mutex<Repository>>,
pull_request: &GitPushPullRequest,
) -> eyre::Result<()> {
tracing::trace!("creating branch");
self.git_provider
.create_branch(repo, &pull_request.name)
.await
}
async fn create_pull_request(
&self,
repo: Arc<Mutex<Repository>>,
repo_name: &String,
pull_request: &GitPushPullRequest,
) -> eyre::Result<()> {
let (owner, repo_name) = repo_name
.split_once("/")
.ok_or(eyre::anyhow!("repo is not a valid format"))?;
tracing::trace!("push_branch");
self.git_provider
.push_branch(repo, &pull_request.name)
.await?;
tracing::trace!("create_pull_request");
self.github_client
.create_pull_request(&owner.into(), &repo_name.into(), &pull_request.name)
.await
}
}

View File

@@ -0,0 +1,42 @@
pub mod github_client;
pub mod github_provider;
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use git2::Repository;
use tokio::sync::Mutex;
use crate::schema::models::GitPushPullRequest;
#[async_trait]
pub trait GitHubClient {
async fn get_clone_url(&self, owner: String, repo_name: String) -> eyre::Result<String>;
async fn create_pull_request(
&self,
owner: &String,
repo_name: &String,
pull_request_name: &String,
) -> eyre::Result<()>;
}
pub type DynGitHubClient = Arc<dyn GitHubClient + Send + Sync>;
#[async_trait]
pub trait GitHubProvider {
async fn clone_from_qualified(&self, repo: &String) -> eyre::Result<(PathBuf, Repository)>;
async fn create_branch(
&self,
repo: Arc<Mutex<Repository>>,
branch: &GitPushPullRequest,
) -> eyre::Result<()>;
async fn create_pull_request(
&self,
repo: Arc<Mutex<Repository>>,
repo_name: &String,
pull_request: &GitPushPullRequest,
) -> eyre::Result<()>;
}
pub type DynGitHubProvider = Arc<dyn GitHubProvider + Send + Sync>;

View File

@@ -6,6 +6,7 @@ use tokio::sync::Mutex;
pub mod git;
pub mod gitea;
pub mod github;
#[async_trait]
pub trait GitProvider {

View File

@@ -0,0 +1,59 @@
use std::{path::PathBuf, sync::Arc};
use tokio::sync::Mutex;
use crate::{
executor::executor::DynExecutor,
git::{github::DynGitHubProvider, DynGitProvider},
schema::models::{Action, GitHub},
};
pub struct GitHubSelector {
github_provider: DynGitHubProvider,
git_provider: DynGitProvider,
executor: DynExecutor,
}
impl GitHubSelector {
pub fn new(
github_provider: DynGitHubProvider,
git_provider: DynGitProvider,
executor: DynExecutor,
) -> Self {
Self {
github_provider,
git_provider,
executor,
}
}
pub async fn run(
&self,
git: &GitHub,
action_path: &PathBuf,
action: &Action,
) -> eyre::Result<()> {
tracing::info!("fetching repos");
for repo in &git.repositories {
let gp = self.github_provider.clone();
let (path, repo) = gp.clone_from_qualified(repo).await?;
let repo = Arc::new(Mutex::new(repo));
if let Some(push) = &git.push {
self.git_provider
.create_branch(repo.clone(), &push.pull_request.name)
.await?;
}
self.executor.execute(&path, action_path, action).await?;
if let Some(push) = &git.push {
self.git_provider
.push_branch(repo, &push.pull_request.name)
.await?;
}
}
Ok(())
}
}

View File

@@ -1,3 +1,4 @@
pub mod git_selector;
pub mod gitea_selector;
pub mod github_selector;

View File

@@ -10,10 +10,17 @@ use octopush_core::{
provider::DefaultGiteaProvider,
DynGiteaProvider,
},
github::{
github_client::{DefaultGitHubClient, DefaultGitHubClientOptions},
github_provider::DefaultGitHubProvider,
DynGitHubProvider,
},
DynGitProvider,
},
schema::parser::{DefaultSchemaParser, DynSchemaParser},
selectors::{git_selector::GitSelector, gitea_selector::GiteaSelector},
selectors::{
git_selector::GitSelector, gitea_selector::GiteaSelector, github_selector::GitHubSelector,
},
storage::{local::LocalStorageEngine, DynStorageEngine},
};
@@ -26,13 +33,16 @@ pub struct ServiceRegister {
pub gitea_provider: DynGiteaProvider,
pub git_selector: Arc<GitSelector>,
pub gitea_selector: Arc<GiteaSelector>,
pub github_provider: DynGitHubProvider,
pub github_selector: Arc<GitHubSelector>,
}
impl ServiceRegister {
pub fn new(
git_provider_options: LocalGitProviderOptions,
gitea_client_options: DefaultGiteaClientOptions,
) -> Self {
github_client_options: DefaultGitHubClientOptions,
) -> eyre::Result<Self> {
let storage_engine = Arc::new(LocalStorageEngine::new("/tmp/octopush".into()));
let git_provider = Arc::new(LocalGitProvider::new(
git_provider_options,
@@ -53,8 +63,19 @@ impl ServiceRegister {
git_provider.clone(),
executor.clone(),
));
let github_client = Arc::new(DefaultGitHubClient::new(&github_client_options)?);
let github_provider = Arc::new(DefaultGitHubProvider::new(
git_provider.clone(),
storage_engine.clone(),
github_client.clone(),
));
let github_selector = Arc::new(GitHubSelector::new(
github_provider.clone(),
git_provider.clone(),
executor.clone(),
));
Self {
Ok(Self {
storage_engine,
git_provider,
schema_parser,
@@ -63,7 +84,9 @@ impl ServiceRegister {
gitea_provider,
git_selector,
gitea_selector,
}
github_provider,
github_selector,
})
}
pub async fn cleanup(self) -> eyre::Result<()> {