Compare commits

..

14 commits

Author SHA1 Message Date
1bc34011c1 release to forgejo works in an early state
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m53s
2024-04-25 15:44:55 +02:00
0e4d694b35 fix config parser
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m41s
2024-04-25 14:33:48 +02:00
926f72b87e
update cargo ci
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m51s
2024-03-03 14:02:34 +01:00
51a50cd13f
Merge branch 'master' into devel
All checks were successful
cargo devel CI / cargo CI (push) Successful in 4m1s
2024-02-27 09:15:27 +01:00
PlexSheep
90312a457b automatic cargo CI changes 2024-02-25 00:18:12 +00:00
2f8442bf74
we can tag and push, but the api does nothing?
All checks were successful
cargo devel CI / cargo CI (push) Successful in 1m51s
2024-02-25 01:16:19 +01:00
0c2f270336
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
All checks were successful
cargo devel CI / cargo CI (push) Successful in 1m54s
2024-02-25 00:15:13 +01:00
9839b8b034
fix yaml parser 2024-02-25 00:15:11 +01:00
PlexSheep
23c51ac1da automatic cargo CI changes 2024-02-24 23:03:06 +00:00
7d4a3986a9
in THEORY maybe we could release if we ask nicely
All checks were successful
cargo devel CI / cargo CI (push) Successful in 2m3s
2024-02-25 00:01:03 +01:00
2c26a65a65
security
All checks were successful
cargo devel CI / cargo CI (push) Successful in 1m55s
2024-02-24 18:48:30 +01:00
3e9b58da62
codeberg mirror note
All checks were successful
cargo devel CI / cargo CI (push) Successful in 1m59s
2024-02-24 16:58:24 +01:00
6e9183af20
update readme
All checks were successful
cargo devel CI / cargo CI (push) Successful in 2m1s
2024-02-24 16:52:16 +01:00
6f70057aba
typo
All checks were successful
cargo devel CI / cargo CI (push) Successful in 2m59s
2024-02-24 14:52:29 +01:00
20 changed files with 378 additions and 128 deletions

View file

@ -1,4 +1,5 @@
---
version:
!cargo
changelog:
enable: true
git-log: true
@ -12,17 +13,18 @@ uses:
- cscherr
api:
github:
type: github
endpoint: https://github.com
auth:
user: PlexSheep
pass:
env: TOKEN_GH
# github:
# type: github
# repository: autocrate
# auth:
# user: PlexSheep
# pass:
# !env TOKEN_GH
cscherr:
type: forgejo
endpoint: https://git.cscherr.de
repository: autocrate
auth:
user: PlexSheep
pass:
env: TOKEN_CSCHERR
!env TOKEN_CSCHERR

2
.env Normal file
View file

@ -0,0 +1,2 @@
TOKEN_CSCHERR=test
TOKEN_GH=test

View file

@ -31,13 +31,13 @@ jobs:
echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml
cat ~/.cargo/config.toml
- name: cargo clippy check
run: cargo clippy --all-features --all-targets
run: cargo clippy --all-features --all-targets --workspace
- name: cargo clippy fix
run: cargo clippy --fix --all-features --all-targets
run: cargo clippy --fix --all-features --all-targets --workspace
- name: cargo fmt
run: cargo fmt --all
- name: cargo test
run: cargo test --all-features --all-targets
run: cargo test --all-features --all-targets --workspace
- name: commit back to repository
uses: https://github.com/stefanzweifel/git-auto-commit-action@v5
with:

View file

@ -32,13 +32,13 @@ jobs:
echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml
cat ~/.cargo/config.toml
- name: cargo clippy check
run: cargo clippy --all-features --all-targets
run: cargo clippy --all-features --all-targets --workspace
- name: cargo clippy fix
run: cargo clippy --fix --all-features --all-targets
run: cargo clippy --fix --all-features --all-targets --workspace
- name: cargo fmt
run: cargo fmt --all
- name: cargo test
run: cargo test --all-features --all-targets
run: cargo test --all-features --all-targets --workspace
- name: commit back to repository
uses: stefanzweifel/git-auto-commit-action@v5
with:

View file

@ -1,6 +1,6 @@
[package]
name = "autocrate"
version = "0.1.0-prealpha.3"
version = "0.1.0-prealpha.4"
edition = "2021"
publish = true
authors = ["Christoph J. Scherr <software@cscherr.de>"]
@ -25,10 +25,13 @@ async-trait = "0.1.77"
# cargo = "0.76.0"
clap = { version = "4.4.18", features = ["derive", "help"] }
clap-verbosity-flag = "2.1.2"
forgejo-api = "0.1.0"
futures = "0.3.30"
git2 = "0.18.1"
libpt = { version = "0.3.11", features = ["log"] }
reqwest = "0.11.24"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.116"
serde_yaml = "0.9.30"
tempfile = "3.9.0"
thiserror = "1.0.56"

View file

@ -21,6 +21,7 @@ The software is built in Rust, and offers integration for Rust Projects with Car
In the future, using other tools and specifying custom scripts will become possible.
* [Original Repository](https://git.cscherr.de/PlexSheep/Autocrate)
* [Codeberg Mirror](https://codeberg.org/PlexSheep/autocrate)
* [GitHub Mirror](https://github.com/PlexSheep/Autocrate)
* [crates.io](https://crates.io/crates/autocrate)
* [docs.rs](https://docs.rs/crate/autocrate/)
@ -160,9 +161,17 @@ are still missing or experimental
## Contributing
I'd be very happy to get contributions! Although the master repository is on
my self hosted git server, you're free to create issues, PRs and so on on
GitHub. If enough activity comes around, moving to GitHub Codeberg might be a
my self hosted git server, you're free to create issues, PRs and so on
GitHub. If enough activity comes around, moving to GitHub Codeberg might be a
good idea.
If you have any questions, use issues and discussions tabs or write me an email
to [software@cscherr.de](mailto:software@cscherr.de)
## Security
If you find a security issue with this repository, it would be best if you sent
me a mail to [software@cscherr.de](mailto:software@cscherr.de) or reported it on
GitHub.
See [`SECURITY`](SECURITY.md).

14
SECURITY.md Normal file
View file

@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
Only the latest release is currently supported.
## Reporting a Vulnerability
It would be best if you reported any found security vulnerabilities on the GitHub mirror.
If you want to send something encrypted, use [this](https://static.cscherr.de/keys/software@cscherr.de.asc) key.
You can always reach me on [software@cscherr.de](mailto:software@cscherr.de), but sadly, I'm struggeling with PGP
encryption in my mail client. I use protonmail to host my EMail services, so if you send me something from protonmail,
it will be end to end encrypted.

View file

@ -17,6 +17,7 @@ impl Changelog {
Ok(Changelog { git_log })
}
// TODO: use libgit2 instead of the cli interface
fn make_git_log(cfg: &Config) -> Result<Option<String>> {
if !cfg.yaml.changelog.enable {
return Ok(None);
@ -35,7 +36,6 @@ impl Changelog {
return Err(ChangelogError::GitBadStatus(out.status, buf).into());
}
dbg!(&buf);
Ok(Some(buf))
}

View file

@ -62,9 +62,11 @@ pub enum Commands {
message: Option<Vec<String>>,
/// generate and add a changelog
#[arg(short, long)]
changelog: bool,
/// publish after releasing
#[arg(short, long)]
publish: bool,
},
/// Publish to a package registry

View file

@ -1,8 +1,11 @@
use std::{collections::HashMap, fmt::Debug, fs::File, io::BufReader, path::PathBuf};
use std::str::FromStr;
use std::{
collections::HashMap, fmt::Debug, fs::File, io::BufReader, path::PathBuf, process::Command,
};
use git2;
use libpt::log::{debug, error, trace};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::error::*;
@ -15,7 +18,7 @@ pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> {
fn check(&self) -> Result<()>;
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Changelog {
pub enable: bool,
#[serde(alias = "git-log")]
@ -27,7 +30,7 @@ impl YamlConfigSection for Changelog {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UseCargo {
pub publish: bool,
pub registries: Vec<String>,
@ -38,7 +41,7 @@ impl YamlConfigSection for UseCargo {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Uses {
cargo: UseCargo,
}
@ -49,13 +52,17 @@ impl YamlConfigSection for Uses {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Pass {
/// pass specified as plainext
#[serde(alias = "pass_text")]
Text(String),
/// pass to be loaded from an env var
#[serde(alias = "pass_env")]
Env(String),
/// pass to be loaded from a file
#[serde(alias = "pass_file")]
File(PathBuf),
}
impl Pass {
@ -88,7 +95,7 @@ impl YamlConfigSection for Pass {
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ApiAuth {
pub user: String,
pub pass: Pass,
@ -100,20 +107,31 @@ impl YamlConfigSection for ApiAuth {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Api {
#[serde(alias = "type")]
pub server_type: ApiType,
pub endpoint: Url,
/// May be left empty if the [ApiType] is [Github](ApiType::Github).
pub endpoint: Option<Url>,
/// May be left empty if the Api does not need auth or the auth is part of the
/// [endpoint](Api::endpoint) [Url].
pub auth: Option<ApiAuth>,
/// Name of the repository on the Git server, as git itself has no concept of repository name
pub repository: String,
}
impl YamlConfigSection for Api {
fn check(&self) -> Result<()> {
self.server_type.check()?;
match self.endpoint.socket_addrs(|| None) {
Ok(_) => (),
Err(err) => return Err(err.into()),
if self.server_type != ApiType::Github {
if self.auth.is_none() {
return Err(ConfigError::NoEndpointSet.into());
}
match self.endpoint.clone().unwrap().socket_addrs(|| None) {
Ok(_) => (),
Err(err) => return Err(err.into()),
}
} else if let Some(_url) = &self.endpoint {
return Err(ConfigError::EndpointSetButNotNeeded.into());
}
if self.auth.is_some() {
self.auth.clone().unwrap().check()?;
@ -122,7 +140,7 @@ impl YamlConfigSection for Api {
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum ApiType {
#[serde(alias = "gitea")]
Gitea,
@ -133,22 +151,98 @@ pub enum ApiType {
#[serde(alias = "forgejo")]
Forgejo,
}
impl ApiType {
pub fn default_endpoint(&self) -> Option<Url> {
match self {
Self::Github => Some(Url::from_str("https://github.com").unwrap()),
_ => None,
}
}
}
impl YamlConfigSection for ApiType {
fn check(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Version {
Text(String),
Cmd(String),
Cargo,
}
impl Version {
pub fn get_version(&self) -> String {
// TODO: Error handling
match self {
Self::Text(ver) => ver.clone(),
Self::Cmd(shell_command) => {
match Command::new("/bin/bash")
.arg("-c")
.arg(shell_command)
.output()
{
Ok(output) => {
// TODO: check status
String::from_utf8(output.stdout).unwrap()
}
Err(err) => {
panic!("{err:?}");
}
}
}
Self::Cargo => {
match Command::new("/bin/bash")
.arg("-c")
.arg(r#"cat Cargo.toml | rg '^\s*version\s*=\s*"([^"]*)"\s*$' -or '$1'"#)
.output()
{
Ok(output) => {
// TODO: check status
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
Err(err) => {
panic!("{err:?}");
}
}
},
}
}
}
impl YamlConfigSection for Version {
fn check(&self) -> Result<()> {
match self {
Self::Text(_) => (),
Self::Cmd(_cmd) => {
// TODO: get the version with a command
todo!("verion from cmd not implemented")
}
Self::Cargo => {
// TODO: get the version as specified in a Cargo.toml
todo!("verion from cargo not implemented")
}
}
Ok(())
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct YamlConfig {
pub changelog: Changelog,
pub uses: Uses,
pub api: HashMap<String, Api>,
pub version: Version,
}
impl YamlConfigSection for YamlConfig {
fn check(&self) -> Result<()> {
self.changelog.check()?;
self.uses.check()?;
self.version.check()?;
for api in self.api.values() {
api.check()?;
}

View file

@ -32,6 +32,8 @@ pub enum ServerApiError {
InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
#[error(transparent)]
ReqwestErr(#[from] reqwest::Error),
#[error(transparent)]
ForgejoApiError(#[from] forgejo_api::ForgejoError)
}
#[derive(Error, Debug)]
@ -61,4 +63,8 @@ pub enum ConfigError {
EnvNotSet(String),
#[error("Bad value for environment variable: {0}")]
BadEnv(#[from] VarError),
#[error("An endpoint was set for an ApiType that does not require one")]
EndpointSetButNotNeeded,
#[error("No endpoint was set for an ApiType that requires one")]
NoEndpointSet
}

52
src/git/mod.rs Normal file
View file

@ -0,0 +1,52 @@
use std::process::Command;
use git2;
use crate::{config::Config, error::Result};
pub async fn tag(cfg: &Config) -> Result<git2::Tag> {
// TODO: error handling
// TODO: allow force
// TODO: allow setting a message
// TODO: maybe using git as cmd is fancier?
let target = cfg
.repo
.find_object(
cfg.repo.head().unwrap().target().unwrap(),
Some(git2::ObjectType::Commit),
)
.unwrap();
let tagger = cfg.repo.signature().expect("could not get signature");
let message = String::new();
let force = true;
let tag = cfg
.repo
.tag(
&cfg.yaml.version.get_version(),
// "importantversion",
&target,
&tagger,
&message,
force,
)
.unwrap();
let tag: git2::Tag = cfg.repo.find_tag(tag).unwrap();
Ok(tag)
}
pub async fn push(_cfg: &Config) -> Result<()> {
// TODO: error handling
// TODO: maybe using git as lib is fancier?
Command::new("git").arg("push").status().unwrap();
Ok(())
}
pub async fn get_commit_sig(cfg: &Config) -> Result<String> {
// TODO: error handling
// TODO: maybe using git as cmd is fancier?
let target = cfg
.repo
.find_commit(cfg.repo.head().unwrap().target().unwrap())
.unwrap();
Ok(target.id().to_string())
}

View file

@ -1,6 +1,7 @@
pub mod changelog;
pub mod config;
pub mod error;
pub mod git;
pub mod publish;
pub mod release;
pub mod serverapi;

View file

@ -7,7 +7,7 @@ use autocrate::{
error::*,
publish::publish,
release::release,
serverapi::init_servers,
serverapi::ApiCollection,
};
#[tokio::main]
@ -20,10 +20,12 @@ async fn main() -> Result<()> {
println!("{}", Changelog::build(&cfg)?);
}
Commands::Release { .. } => {
let mut apis = init_servers(&cfg).await?;
// TODO: check if repo is dirty and create a commit with a given option
let mut apis = ApiCollection::build(&cfg).await?;
release(&cfg, &mut apis).await?;
}
Commands::Publish { .. } => {
// TODO: check if repo is dirty and create a commit with a given option
publish(&cfg).await?;
}
Commands::Version {} => {

View file

@ -1,5 +1,13 @@
use crate::{config::Config, error::*, serverapi::ApiCollection};
use crate::{
config::Config,
error::*,
git::{get_commit_sig, push, tag},
serverapi::ApiCollection,
};
use futures::{self, stream::FuturesUnordered, StreamExt};
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ReleaseContext {
pub draft: bool,
pub prerelease: bool,
@ -10,13 +18,40 @@ pub struct ReleaseContext {
pub commit_sig: String,
}
pub async fn release(cfg: &Config, _apis: &mut ApiCollection) -> Result<()> {
// TODO: git tag
// TODO: push to each server
pub async fn release(cfg: &Config, apis: &mut ApiCollection) -> Result<()> {
// TODO: Error handling
let tag = tag(cfg).await?.name().unwrap().to_string();
let commit_sig = get_commit_sig(cfg).await?;
push(cfg).await?; // we assume that we only need to push the current branch to the singular
// remote, expecting that the repositories are somehow mirrored
// TODO: push to multiple remotes?
// TODO: release to each server
tag(cfg).await?;
todo!();
let mut results = FuturesUnordered::new();
for api in apis.iter_mut() {
// TODO: check that auth exists
let specific_rc = ReleaseContext {
draft: true,
prerelease: true,
username: api
.get_cfg()
.clone()
.auth
.expect("no auth but trying to publish")
.user,
repository: api.get_cfg().repository.clone(),
text: crate::changelog::Changelog::build(cfg)?.to_string(),
tag: tag.clone(),
commit_sig: commit_sig.clone(),
};
results.push(api.push_release(specific_rc));
}
// wait for the release requests to finish
while let Some(result) = results.next().await {
if result.is_err() {
return Err(result.unwrap_err());
}
}
// TODO: check that the release is made
// TODO: generate artifacts
@ -25,7 +60,3 @@ pub async fn release(cfg: &Config, _apis: &mut ApiCollection) -> Result<()> {
Ok(())
}
async fn tag(_cfg: &Config) -> Result<()> {
todo!()
}

View file

@ -1,37 +1,34 @@
use std::str::FromStr;
use crate::{
config::{Api, Config},
error::*,
serverapi::{PublishContext, ReleaseContext, ServerApi},
};
use async_trait::async_trait;
use forgejo_api;
use libpt::log::debug;
use reqwest::{
header::{HeaderMap, HeaderValue},
Client, Url,
};
use serde_json;
pub struct Forgejo {
cfg: Api,
client: Client,
api_wrapper: forgejo_api::Forgejo,
}
impl Forgejo {
pub async fn build(api: &Api) -> Result<Self> {
let mut headers: HeaderMap = HeaderMap::new();
// may be left empty if we only do reads from publically accessible urls
if api.auth.is_some() {
let _ = headers.insert(
"Authorization",
HeaderValue::from_str(api.auth.clone().unwrap().pass.get_pass()?.as_str())
.map_err(ServerApiError::from)?,
);
}
let client = super::client_builder()
.default_headers(headers)
.build()
.map_err(ServerApiError::from)?;
let api_wrapper: forgejo_api::Forgejo = forgejo_api::Forgejo::new(
forgejo_api::Auth::Token(&api.auth.clone().unwrap().pass.get_pass()?),
api.endpoint.clone().unwrap(),
)
.map_err(ServerApiError::from)?;
Ok(Self {
cfg: api.clone(),
client,
api_wrapper,
})
}
}
@ -39,45 +36,31 @@ impl Forgejo {
#[async_trait]
impl ServerApi for Forgejo {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
Ok(())
}
async fn push_release(&mut self, rc: &ReleaseContext) -> Result<()> {
let raw_url = format!(
"{}/api/v1/repos/{}/{}/releases",
self.cfg.endpoint, rc.username, rc.repository
);
let url = Url::parse(&raw_url).map_err(ServerApiError::from)?;
let body = format!(
r#"
{{
"body": "{}",
"draft": {},
"name": "{}",
"prerelease": {},
"tag_name": "{}",
"target_commitish": "{}"
}}
"#,
rc.text, rc.draft, rc.tag, rc.prerelease, rc.tag, rc.commit_sig
);
let request = self
.client
.post(url)
.body(body)
.build()
.map_err(ServerApiError::from)?;
let _response = self
.client
.execute(request)
async fn push_release(&mut self, rc: ReleaseContext) -> Result<()> {
let body: forgejo_api::structs::CreateReleaseOption =
forgejo_api::structs::CreateReleaseOption {
body: Some(rc.text),
draft: Some(rc.draft),
name: Some(rc.tag.clone()),
prerelease: Some(rc.prerelease),
tag_name: rc.tag,
target_commitish: Some(rc.commit_sig),
};
self.api_wrapper
.repo_create_release(&rc.username, &rc.repository, body)
.await
.map_err(ServerApiError::from)?;
Ok(())
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release_artifact(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
async fn push_pkg(&mut self, _pc: PublishContext) -> Result<()> {
todo!()
}
fn get_cfg(&self) -> &Api {
&self.cfg
}
}

View file

@ -14,15 +14,18 @@ impl ServerApi for Gitea {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release_artifact(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
async fn push_pkg(&mut self, _pc: PublishContext) -> Result<()> {
todo!()
}
fn get_cfg(&self) -> &Api {
&self.cfg
}
}
impl Gitea {

View file

@ -14,15 +14,18 @@ impl ServerApi for Github {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release_artifact(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
async fn push_pkg(&mut self, _pc: PublishContext) -> Result<()> {
todo!()
}
fn get_cfg(&self) -> &Api {
&self.cfg
}
}
impl Github {

View file

@ -14,15 +14,18 @@ impl ServerApi for Gitlab {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
async fn push_release_artifact(&mut self, _rc: ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
async fn push_pkg(&mut self, _pc: PublishContext) -> Result<()> {
todo!()
}
fn get_cfg(&self) -> &Api {
&self.cfg
}
}
impl Gitlab {

View file

@ -1,8 +1,10 @@
use std::ops::{Deref, DerefMut};
use async_trait::async_trait;
use reqwest::ClientBuilder;
use crate::{
config::{ApiType, Config},
config::{self, ApiType, Config},
error::*,
publish::PublishContext,
release::ReleaseContext,
@ -18,7 +20,6 @@ use github::*;
use gitlab::*;
pub static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
pub type ApiCollection = Vec<Box<dyn ServerApi>>;
// NOTE: in stable rust, traits can normally not contain async methods,
// see [here](https://stackoverflow.com/questions/65921581/how-can-i-define-an-async-method-in-a-trait).
@ -26,35 +27,74 @@ pub type ApiCollection = Vec<Box<dyn ServerApi>>;
#[async_trait]
pub trait ServerApi {
async fn init(&mut self, cfg: &Config) -> Result<()>;
async fn push_release(&mut self, rc: &ReleaseContext) -> Result<()>;
async fn push_release_artifact(&mut self, rc: &ReleaseContext) -> Result<()>;
async fn push_pkg(&mut self, pc: &PublishContext) -> Result<()>;
async fn push_release(&mut self, rc: ReleaseContext) -> Result<()>;
async fn push_release_artifact(&mut self, rc: ReleaseContext) -> Result<()>;
async fn push_pkg(&mut self, pc: PublishContext) -> Result<()>;
fn get_cfg(&self) -> &config::Api;
}
pub(crate) type ApiCollectionInner = Vec<Box<dyn ServerApi>>;
pub struct ApiCollection {
collection: ApiCollectionInner,
}
impl ApiCollection {
pub async fn build(cfg: &Config) -> Result<Self> {
let mut collection: ApiCollectionInner = ApiCollectionInner::new();
for api in &cfg.yaml.api {
match api.1.server_type {
ApiType::Gitea => {
collection.push(Box::new(Gitea::build(api.1).await?));
}
ApiType::Gitlab => {
collection.push(Box::new(Gitlab::build(api.1).await?));
}
ApiType::Github => {
collection.push(Box::new(Github::build(api.1).await?));
}
ApiType::Forgejo => {
collection.push(Box::new(Forgejo::build(api.1).await?));
}
}
}
for api in collection.iter_mut() {
api.init(cfg).await?;
}
Ok(ApiCollection { collection })
}
pub fn collection(&self) -> &ApiCollectionInner {
self.collection.as_ref()
}
pub fn collection_mut(&mut self) -> &mut ApiCollectionInner {
self.collection.as_mut()
}
}
pub fn client_builder() -> ClientBuilder {
ClientBuilder::new().user_agent(USER_AGENT)
}
pub async fn init_servers(cfg: &Config) -> Result<ApiCollection> {
let mut collection: ApiCollection = ApiCollection::new();
for api in &cfg.yaml.api {
match api.1.server_type {
ApiType::Gitea => {
collection.push(Box::new(Gitea::build(api.1).await?));
}
ApiType::Gitlab => {
collection.push(Box::new(Gitlab::build(api.1).await?));
}
ApiType::Github => {
collection.push(Box::new(Github::build(api.1).await?));
}
ApiType::Forgejo => {
collection.push(Box::new(Forgejo::build(api.1).await?));
}
}
// trait iimplementations for easy use of ApiCollection follow
impl IntoIterator for ApiCollection {
fn into_iter(self) -> Self::IntoIter {
self.collection.into_iter()
}
type Item = Box<dyn ServerApi>;
type IntoIter = std::vec::IntoIter<Self::Item>;
}
impl Deref for ApiCollection {
type Target = [Box<dyn ServerApi>];
fn deref(&self) -> &Self::Target {
&self.collection[..]
}
}
impl DerefMut for ApiCollection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.collection[..]
}
for api in collection.iter_mut() {
api.init(cfg).await?;
}
Ok(collection)
}