Compare commits

...

24 commits

Author SHA1 Message Date
6f70057aba
typo
All checks were successful
cargo devel CI / cargo CI (push) Successful in 2m59s
2024-02-24 14:52:29 +01:00
PlexSheep
a42393864e automatic cargo CI changes 2024-02-24 13:44:06 +00:00
3e0d32721b
remove many deps
All checks were successful
cargo devel CI / cargo CI (push) Successful in 1m55s
2024-02-24 14:41:56 +01:00
45ba3f6b97
contexts for api actions
Some checks failed
cargo devel CI / cargo CI (push) Failing after 4m0s
2024-02-24 14:33:58 +01:00
39538b7b01
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m51s
2024-02-24 14:11:00 +01:00
d02830b656
a lot more to do 2024-02-24 14:10:44 +01:00
PlexSheep
19d5d11b05 automatic cargo CI changes 2024-02-24 12:28:29 +00:00
bd04eea9cc
pass enum
All checks were successful
cargo devel CI / cargo CI (push) Successful in 4m4s
2024-02-24 13:24:24 +01:00
f12f5763d1
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m42s
2024-02-19 22:08:00 +01:00
77f0aabdfc
important changes 2024-02-19 22:07:51 +01:00
PlexSheep
0abcdff5af automatic cargo CI changes 2024-02-16 19:08:27 +00:00
acc5381dd1
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
All checks were successful
cargo devel CI / cargo CI (push) Successful in 3m48s
2024-02-16 20:04:39 +01:00
1c965da645
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel 2024-02-16 20:03:07 +01:00
b2bd3a9dde
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m38s
2024-02-16 20:01:17 +01:00
0f1b82c4c9
release and server api skeletons 2024-02-16 20:00:55 +01:00
PlexSheep
48008c4d7a automatic cargo CI changes 2024-02-16 18:01:54 +00:00
2568d96ac3
module structure
All checks were successful
cargo devel CI / cargo CI (push) Successful in 3m34s
2024-02-16 18:58:20 +01:00
660d4bd9da
Merge branch 'devel' of https://git.cscherr.de/PlexSheep/autocrate into devel
All checks were successful
cargo devel CI / cargo CI (push) Successful in 3m43s
2024-02-16 18:40:23 +01:00
c5c79b784a
tokio 2024-02-16 18:37:19 +01:00
PlexSheep
db14cb7a8f automatic cargo CI changes 2024-02-16 15:43:33 +00:00
02c51d6e4a
ci add clippy rustup
All checks were successful
cargo devel CI / cargo CI (push) Successful in 8m32s
2024-02-16 16:35:04 +01:00
8c9e893d44
clippy ci
Some checks failed
cargo devel CI / cargo CI (push) Failing after 23s
2024-02-16 16:12:00 +01:00
0f6ca5d6f2
fix ci
All checks were successful
cargo devel CI / cargo CI (push) Successful in 6m57s
2024-02-09 18:37:14 +01:00
6267b0af45
fix release script
All checks were successful
cargo devel CI / cargo CI (push) Successful in 4m32s
2024-02-02 00:06:32 +01:00
20 changed files with 447 additions and 70 deletions

View file

@ -16,11 +16,13 @@ api:
type: github type: github
endpoint: https://github.com endpoint: https://github.com
auth: auth:
user: myUserName user: PlexSheep
pass: token_superimportantsecret pass:
myserv: env: TOKEN_GH
type: gitea cscherr:
type: forgejo
endpoint: https://git.cscherr.de endpoint: https://git.cscherr.de
auth: auth:
user: myUserName user: PlexSheep
pass: importantsecrettoken pass:
env: TOKEN_CSCHERR

View file

@ -6,7 +6,7 @@ on:
# - '!master' # - '!master'
jobs: jobs:
CI: format:
name: cargo CI name: cargo CI
permissions: permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the # Give the default GITHUB_TOKEN write permission to commit and push the
@ -16,9 +16,11 @@ jobs:
- name: get repo - name: get repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: install rust - name: install rust
uses: dtolnay/rust-toolchain@stable uses: https://github.com/dtolnay/rust-toolchain@stable
- name: install additional rust things - name: install additional rust things
run: rustup component add rustfmt run: |
rustup component add rustfmt
rustup component add clippy
- name: config custom registry - name: config custom registry
run: | run: |
mkdir -p ~/.cargo/ mkdir -p ~/.cargo/
@ -28,16 +30,16 @@ jobs:
echo '[registries.cscherr]' >> ~/.cargo/config.toml echo '[registries.cscherr]' >> ~/.cargo/config.toml
echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml
cat ~/.cargo/config.toml cat ~/.cargo/config.toml
- name: cargo check - name: cargo clippy check
run: cargo check --all-features --all-targets run: cargo clippy --all-features --all-targets
- name: cargo fix - name: cargo clippy fix
run: cargo fix --all-features --all-targets run: cargo clippy --fix --all-features --all-targets
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all run: cargo fmt --all
- name: cargo test - name: cargo test
run: cargo test --all-features --all-targets run: cargo test --all-features --all-targets
- name: commit back to repository - name: commit back to repository
uses: stefanzweifel/git-auto-commit-action@v5 uses: https://github.com/stefanzweifel/git-auto-commit-action@v5
with: with:
# Optional. Commit message for the created commit. # Optional. Commit message for the created commit.
# Defaults to "Apply automatic changes" # Defaults to "Apply automatic changes"

View file

@ -19,7 +19,9 @@ jobs:
- name: install rust - name: install rust
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: install additional rust things - name: install additional rust things
run: rustup component add rustfmt run: |
rustup component add rustfmt
rustup component add clippy
- name: config custom registry - name: config custom registry
run: | run: |
mkdir -p ~/.cargo/ mkdir -p ~/.cargo/
@ -29,10 +31,10 @@ jobs:
echo '[registries.cscherr]' >> ~/.cargo/config.toml echo '[registries.cscherr]' >> ~/.cargo/config.toml
echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml echo 'index = "https://git.cscherr.de/PlexSheep/_cargo-index.git"' >> ~/.cargo/config.toml
cat ~/.cargo/config.toml cat ~/.cargo/config.toml
- name: cargo check - name: cargo clippy check
run: cargo check --all-features --all-targets run: cargo clippy --all-features --all-targets
- name: cargo fix - name: cargo clippy fix
run: cargo fix --all-features --all-targets run: cargo clippy --fix --all-features --all-targets
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all run: cargo fmt --all
- name: cargo test - name: cargo test

View file

@ -1,6 +1,6 @@
[package] [package]
name = "autocrate" name = "autocrate"
version = "0.1.0-prealpha.1" version = "0.1.0-prealpha.2"
edition = "2021" edition = "2021"
publish = true publish = true
authors = ["Christoph J. Scherr <software@cscherr.de>"] authors = ["Christoph J. Scherr <software@cscherr.de>"]
@ -21,15 +21,18 @@ keywords = [
[dependencies] [dependencies]
anyhow = "1.0.79" anyhow = "1.0.79"
cargo = "0.76.0" async-trait = "0.1.77"
# cargo = "0.76.0"
clap = { version = "4.4.18", features = ["derive", "help"] } clap = { version = "4.4.18", features = ["derive", "help"] }
clap-verbosity-flag = "2.1.2" clap-verbosity-flag = "2.1.2"
git2 = "0.18.1" git2 = "0.18.1"
libpt = { version = "0.3.11", features = ["log"] } libpt = { version = "0.3.11", features = ["log"] }
reqwest = "0.11.24"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_yaml = "0.9.30" serde_yaml = "0.9.30"
tempfile = "3.9.0" tempfile = "3.9.0"
thiserror = "1.0.56" thiserror = "1.0.56"
tokio = { version = "1.36.0", features = ["tokio-macros", "rt-multi-thread", "macros"] }
url = { version = "2.5.0", features = ["serde"] } url = { version = "2.5.0", features = ["serde"] }
[[bin]] [[bin]]

View file

@ -65,21 +65,24 @@ locally, making it readily accessible through your command line interfaces.
Create a YAML file named `.autocrate.yml` (or `.yaml`) in the root of your Git Create a YAML file named `.autocrate.yml` (or `.yaml`) in the root of your Git
repository. It should contain the following parameters (replace the placeholders): repository. It should contain the following parameters (replace the placeholders):
| Parent | Key | Value | Explanation | | Parent | Key | Value | Explanation |
|-----------------|--------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------------| |----------------------|--------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| (root) | `changelog` | list of keys with this as parent (`git-log` etc) | information on how a changelog is generated | | (root) | `changelog` | list of keys with this as parent (`git-log` etc) | information on how a changelog is generated |
| `changelog` | `enable` | `true`/`false` | If false, no changelog will be generated | | `changelog` | `enable` | `true`/`false` | If false, no changelog will be generated |
| `changelog` | `git-log` | `true`/`false` | should a changelog be generated with `git log`? | | `changelog` | `git-log` | `true`/`false` | should a changelog be generated with `git log`? |
| (root) | `uses` | list of keys with this as parent (`cargo` etc) | Marks features to be used by Autocrate | | (root) | `uses` | list of keys with this as parent (`cargo` etc) | Marks features to be used by Autocrate |
| `uses` | `cargo` | list of keys with this as parent (`publish` etc) | tells us that your project uses cargo | | `uses` | `cargo` | list of keys with this as parent (`publish` etc) | tells us that your project uses cargo |
| `cargo` | `publish` | `true`/`false` | should we publish crates? | | `cargo` | `publish` | `true`/`false` | should we publish crates? |
| `cargo` | `registries` | registries see [this](https://doc.rust-lang.org/cargo/reference/registries.html) | A list of registries we should publish to. If empty defaults to `crates.io`. | | `cargo` | `registries` | registries see [this](https://doc.rust-lang.org/cargo/reference/registries.html) | A list of registries we should publish to. If empty defaults to `crates.io`. |
| (root) | `api` | list of names, which each have the same keys | defines the api we talk to | | (root) | `api` | list of names, which each have the same keys | defines the api we talk to |
| `api.NAME` | `type` | one of `gitea`,`github`,`gitlab` (currently only support for `gitea` | Let's us know which api type we are talking to | | `api.NAME` | `type` | one of `gitea`,`github`,`gitlab` (currently only support for `gitea` | Let's us know which api type we are talking to |
| `api.NAME` | `endpoint` | Base URL of the target server | Let's us know which api type we are talking to | | `api.NAME` | `endpoint` | Base URL of the target server | Let's us know which api type we are talking to |
| `api.NAME` | `auth` | list of keys with this as parent (`user` and `pass`) | We probably need authentication on the target server | | `api.NAME` | `auth` | list of keys with this as parent (`user` and `pass`) | We probably need authentication on the target server |
| `api.NAME.auth` | `user` | a string | Which user should we try to authenticate as | | `api.NAME.auth` | `user` | a string | Which user should we try to authenticate as |
| `api.NAME.auth` | `pass` | a string | A secret for authentication o the server, probably a token | | `api.NAME.auth` | `pass` | contains either of `text`, `env` or `file` | sets the secret for authentication with this server |
| `api.NAME.auth.pass` | `text` | a authentication pass as clear text | A secret for authentication of the server, probably a token |
| `api.NAME.auth.pass` | `env` | env var which contains the token | A secret for authentication of the server, probably a token |
| `api.NAME.auth.pass` | `file` | file var which contains the token | A secret for authentication of the server, probably a token |
An example `.autocrate.yaml` could look like this: An example `.autocrate.yaml` could look like this:
@ -102,16 +105,18 @@ api:
endpoint: https://github.com endpoint: https://github.com
auth: auth:
user: PlexSheep user: PlexSheep
pass: token_superimportantsecret pass:
text: token_superimportantsecret
cscherr: cscherr:
type: gitea type: gitea
endpoint: https://git.cscherr.de endpoint: https://git.cscherr.de
auth: auth:
user: PlexSheep user: PlexSheep
pass: Bearer importantsecrettoken pass:
file: secrettoken.txt
``` ```
After Autocrate has been bootstrapped, you it will be released and published After Autocrate has been bootstrapped, it will be released and published
with itself, so you can take a look at this repositories with itself, so you can take a look at this repositories
[`.autocrate.yaml`](./.autocrate.yaml). [`.autocrate.yaml`](./.autocrate.yaml).

View file

@ -7,7 +7,7 @@ BODY="
$(git log $(git describe --tags --abbrev=0)..HEAD --pretty="- %s" --oneline --decorate) $(git log $(git describe --tags --abbrev=0)..HEAD --pretty="- %s" --oneline --decorate)
" "
USER=PlexSheep USER=PlexSheep
git tag "v$NEW_VERSION-test" || echo "could not tag" git tag "v$NEW_VERSION" || echo "could not tag"
curl -X 'POST' \ curl -X 'POST' \
'https://git.cscherr.de/api/v1/repos/PlexSheep/'$REPO'/releases' \ 'https://git.cscherr.de/api/v1/repos/PlexSheep/'$REPO'/releases' \
-H 'accept: application/json' \ -H 'accept: application/json' \

View file

@ -28,7 +28,7 @@ impl Changelog {
let out = cmd.output()?; let out = cmd.output()?;
// FIXME: this does not catch fancy colors, those are from the shell as it seems? I don't // FIXME: this does not catch fancy colors, those are from the shell as it seems? I don't
// get it. // get it.
let buf = String::from_utf8(out.stdout).map_err(|err| ChangelogError::GitUTF8Error(err))?; let buf = String::from_utf8(out.stdout).map_err(ChangelogError::GitUTF8Error)?;
if !out.status.success() { if !out.status.success() {
// TODO: get the stderr for error reporting // TODO: get the stderr for error reporting
// TODO: Make the error more understandable for the user // TODO: Make the error more understandable for the user
@ -43,14 +43,14 @@ impl Changelog {
let mut cmd = Command::new("git"); let mut cmd = Command::new("git");
cmd.arg("describe").arg("--tags").arg("--abbrev=0"); cmd.arg("describe").arg("--tags").arg("--abbrev=0");
let out = cmd.output()?; let out = cmd.output()?;
let buf = String::from_utf8(out.stdout).map_err(|err| ChangelogError::GitUTF8Error(err))?; let buf = String::from_utf8(out.stdout).map_err(ChangelogError::GitUTF8Error)?;
if !out.status.success() { if !out.status.success() {
// TODO: get the stderr for error reporting // TODO: get the stderr for error reporting
// TODO: Make the error more understandable for the user // TODO: Make the error more understandable for the user
return Err(ChangelogError::GitBadStatus(out.status, buf).into()); return Err(ChangelogError::GitBadStatus(out.status, buf).into());
} }
let buf = buf.replace("\n", ""); let buf = buf.replace('\n', "");
return Ok(buf); Ok(buf)
} }
} }

View file

@ -38,6 +38,7 @@ pub struct Cli {
#[derive(Debug, Clone, Subcommand)] #[derive(Debug, Clone, Subcommand)]
pub enum Commands { pub enum Commands {
Changelog {}, Changelog {},
/// Create a new release on the server
Release { Release {
// FIXME: allow taking a message like this: // FIXME: allow taking a message like this:
// `autocrate changelog -m arg1 arg2 arg3` // `autocrate changelog -m arg1 arg2 arg3`
@ -52,14 +53,29 @@ pub enum Commands {
// //
// TODO: // TODO:
// find a way to make this a global option but only usable with specific subcommands // find a way to make this a global option but only usable with specific subcommands
//
// TODO:
// integrate a CHANGELOG.md file
//
/// Message body of the release
#[arg(short, long)] #[arg(short, long)]
message: Option<Vec<String>>, message: Option<Vec<String>>,
/// generate and add a changelog
changelog: bool,
/// publish after releasing
publish: bool,
}, },
/// Publish to a package registry
Publish { Publish {
// see Commands::Release { message } // see Commands::Release { message }
#[arg(short, long)] #[arg(short, long)]
message: Option<Vec<String>>, message: Option<Vec<String>>,
}, },
///
Version {},
Init {},
} }
impl Display for Commands { impl Display for Commands {
@ -71,6 +87,8 @@ impl Display for Commands {
Self::Changelog { .. } => "Changelog", Self::Changelog { .. } => "Changelog",
Self::Release { .. } => "Release", Self::Release { .. } => "Release",
Self::Publish { .. } => "Publish", Self::Publish { .. } => "Publish",
Self::Version { .. } => "Version",
Self::Init { .. } => "Init",
} }
) )
} }
@ -95,6 +113,6 @@ impl Cli {
// less verbose version // less verbose version
Logger::init_mini(Some(ll)).expect("could not initialize Logger"); Logger::init_mini(Some(ll)).expect("could not initialize Logger");
} }
return cli; cli
} }
} }

View file

@ -8,6 +8,7 @@ use url::Url;
use crate::error::*; use crate::error::*;
pub mod cli; pub mod cli;
pub mod packages;
use cli::Cli; use cli::Cli;
pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> { pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> {
@ -48,32 +49,60 @@ impl YamlConfigSection for Uses {
} }
} }
#[derive(Debug, Clone, Deserialize)]
pub enum Pass {
/// pass specified as plainext
Text(String),
/// pass to be loaded from an env var
Env(String),
/// pass to be loaded from a file
File(PathBuf),
}
impl Pass {
/// Get the pass, extracting from the underlying source
pub fn get_pass(&self) -> Result<String> {
self.check()?;
Ok(match self {
Self::Text(pass) => pass.clone(),
Self::Env(key) => std::env::var(key).map_err(ConfigError::from)?,
Self::File(file) => std::fs::read_to_string(file)?,
})
}
}
impl YamlConfigSection for Pass {
fn check(&self) -> Result<()> {
match self {
Self::Text(_) => (),
Self::Env(envvar) => {
if !std::env::var(envvar).map_err(ConfigError::from)?.is_empty() {
} else {
return Err(ConfigError::EnvNotSet(envvar.clone()).into());
}
}
Self::File(file) => {
if !file.exists() {
return Err(ConfigError::PassFileDoesNotExist(file.clone()).into());
}
}
};
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct ApiAuth { pub struct ApiAuth {
pub user: String, pub user: String,
pub pass: Option<String>, pub pass: Pass,
pub pass_file: Option<PathBuf>,
} }
impl YamlConfigSection for ApiAuth { impl YamlConfigSection for ApiAuth {
fn check(&self) -> Result<()> { fn check(&self) -> Result<()> {
if self.pass.is_some() && self.pass_file.is_some() { self.pass.check()?;
let err = ConfigError::YamlApiAuthBothPass(self.clone()).into();
error!("{err}");
return Err(err);
}
if self.pass_file.is_some() {
let file = self.pass_file.clone().unwrap();
if !file.exists() {
return Err(ConfigError::PassFileDoesNotExist(file).into());
}
}
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct Api { pub struct Api {
pub r#type: ApiType, pub server_type: ApiType,
pub endpoint: Url, pub endpoint: Url,
/// May be left empty if the Api does not need auth or the auth is part of the /// May be left empty if the Api does not need auth or the auth is part of the
/// [endpoint](Api::endpoint) [Url]. /// [endpoint](Api::endpoint) [Url].
@ -81,7 +110,7 @@ pub struct Api {
} }
impl YamlConfigSection for Api { impl YamlConfigSection for Api {
fn check(&self) -> Result<()> { fn check(&self) -> Result<()> {
self.r#type.check()?; self.server_type.check()?;
match self.endpoint.socket_addrs(|| None) { match self.endpoint.socket_addrs(|| None) {
Ok(_) => (), Ok(_) => (),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
@ -101,6 +130,8 @@ pub enum ApiType {
Gitlab, Gitlab,
#[serde(alias = "github", alias = "GitHub")] #[serde(alias = "github", alias = "GitHub")]
Github, Github,
#[serde(alias = "forgejo")]
Forgejo,
} }
impl YamlConfigSection for ApiType { impl YamlConfigSection for ApiType {
fn check(&self) -> Result<()> { fn check(&self) -> Result<()> {
@ -147,7 +178,7 @@ impl Debug for Config {
write!( write!(
f, f,
"{}", "{}",
format!( format_args!(
"Config {{yaml: {:?}, repo_path: {:?}}}", "Config {{yaml: {:?}, repo_path: {:?}}}",
self.yaml, self.path self.yaml, self.path
) )
@ -156,7 +187,7 @@ impl Debug for Config {
} }
impl Config { impl Config {
pub fn load(cli: Cli) -> Result<Self> { pub fn load(cli: &Cli) -> Result<Self> {
let repo = match git2::Repository::open_from_env() { let repo = match git2::Repository::open_from_env() {
Ok(repo) => repo, Ok(repo) => repo,
Err(_err) => { Err(_err) => {
@ -197,7 +228,7 @@ impl Config {
yaml, yaml,
repo, repo,
path, path,
cli, cli: cli.clone(),
}) })
} }
} }

3
src/config/packages.rs Normal file
View file

@ -0,0 +1,3 @@
pub enum PackageType {
Cargo,
}

View file

@ -1,4 +1,4 @@
use std::{path::PathBuf, process::ExitStatus, string::FromUtf8Error}; use std::{env::VarError, path::PathBuf, process::ExitStatus, string::FromUtf8Error};
use anyhow; use anyhow;
use thiserror::Error; use thiserror::Error;
@ -20,6 +20,18 @@ pub enum Error {
SerdeYaml(#[from] serde_yaml::Error), SerdeYaml(#[from] serde_yaml::Error),
#[error("Could not generate the changelog")] #[error("Could not generate the changelog")]
ChangelogError(#[from] ChangelogError), ChangelogError(#[from] ChangelogError),
#[error("Server Api error")]
ServerApiError(#[from] ServerApiError),
}
#[derive(Error, Debug)]
pub enum ServerApiError {
#[error(transparent)]
ParseUrl(#[from] url::ParseError),
#[error(transparent)]
InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
#[error(transparent)]
ReqwestErr(#[from] reqwest::Error),
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -45,4 +57,8 @@ pub enum ConfigError {
YamlApiAuthBothPass(ApiAuth), YamlApiAuthBothPass(ApiAuth),
#[error("password provided as file, but does not exist: {0}")] #[error("password provided as file, but does not exist: {0}")]
PassFileDoesNotExist(PathBuf), PassFileDoesNotExist(PathBuf),
#[error("config requires environment variable {0}, but {0} is not set")]
EnvNotSet(String),
#[error("Bad value for environment variable: {0}")]
BadEnv(#[from] VarError),
} }

View file

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

View file

@ -5,22 +5,37 @@ use autocrate::{
Config, Config,
}, },
error::*, error::*,
publish::publish,
release::release,
serverapi::init_servers,
}; };
fn main() -> Result<()> { #[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::cli_parse(); let cli = Cli::cli_parse();
let cfg = Config::load(cli.clone())?; let cfg = Config::load(&cli)?;
match cli.command { match cli.command {
Commands::Changelog { .. } => { Commands::Changelog { .. } => {
println!("{}", Changelog::build(&cfg)?.to_string()); println!("{}", Changelog::build(&cfg)?);
Ok(())
} }
Commands::Release { .. } => { Commands::Release { .. } => {
todo!() let mut apis = init_servers(&cfg).await?;
release(&cfg, &mut apis).await?;
} }
Commands::Publish { .. } => { Commands::Publish { .. } => {
publish(&cfg).await?;
}
Commands::Version {} => {
// TODO: version bump
// TODO: version select interactive
// TODO: version select automated
todo!() todo!()
} }
} Commands::Init { .. } => {
// TODO: create a basic autocrate yaml
todo!()
}
};
Ok(())
} }

7
src/publish/mod.rs Normal file
View file

@ -0,0 +1,7 @@
use crate::{config::Config, error::*};
pub struct PublishContext;
pub async fn publish(_cfg: &Config) -> Result<()> {
todo!()
}

31
src/release/mod.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::{config::Config, error::*, serverapi::ApiCollection};
pub struct ReleaseContext {
pub draft: bool,
pub prerelease: bool,
pub username: String,
pub repository: String,
pub text: String,
pub tag: String,
pub commit_sig: String,
}
pub async fn release(cfg: &Config, _apis: &mut ApiCollection) -> Result<()> {
// TODO: git tag
// TODO: push to each server
// TODO: release to each server
tag(cfg).await?;
todo!();
// TODO: check that the release is made
// TODO: generate artifacts
// TODO: upload artifacts
// TODO: upload artifact signatures
Ok(())
}
async fn tag(_cfg: &Config) -> Result<()> {
todo!()
}

83
src/serverapi/forgejo.rs Normal file
View file

@ -0,0 +1,83 @@
use crate::{
config::{Api, Config},
error::*,
serverapi::{PublishContext, ReleaseContext, ServerApi},
};
use async_trait::async_trait;
use reqwest::{
header::{HeaderMap, HeaderValue},
Client, Url,
};
pub struct Forgejo {
cfg: Api,
client: Client,
}
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)?;
Ok(Self {
cfg: api.clone(),
client,
})
}
}
#[async_trait]
impl ServerApi for Forgejo {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
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)
.await
.map_err(ServerApiError::from)?;
Ok(())
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
todo!()
}
}

32
src/serverapi/gitea.rs Normal file
View file

@ -0,0 +1,32 @@
use async_trait::async_trait;
use super::{PublishContext, ReleaseContext, ServerApi};
use crate::{
config::{Api, Config},
error::*,
};
pub struct Gitea {
cfg: Api,
}
#[async_trait]
impl ServerApi for Gitea {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
todo!()
}
}
impl Gitea {
pub async fn build(api: &Api) -> Result<Self> {
Ok(Self { cfg: api.clone() })
}
}

32
src/serverapi/github.rs Normal file
View file

@ -0,0 +1,32 @@
use async_trait::async_trait;
use super::{PublishContext, ReleaseContext, ServerApi};
use crate::{
config::{Api, Config},
error::*,
};
pub struct Github {
cfg: Api,
}
#[async_trait]
impl ServerApi for Github {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
todo!()
}
}
impl Github {
pub async fn build(api: &Api) -> Result<Self> {
Ok(Self { cfg: api.clone() })
}
}

32
src/serverapi/gitlab.rs Normal file
View file

@ -0,0 +1,32 @@
use async_trait::async_trait;
use super::{PublishContext, ReleaseContext, ServerApi};
use crate::{
config::{Api, Config},
error::*,
};
pub struct Gitlab {
cfg: Api,
}
#[async_trait]
impl ServerApi for Gitlab {
async fn init(&mut self, _cfg: &Config) -> Result<()> {
todo!()
}
async fn push_release(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_release_artifact(&mut self, _rc: &ReleaseContext) -> Result<()> {
todo!()
}
async fn push_pkg(&mut self, _pc: &PublishContext) -> Result<()> {
todo!()
}
}
impl Gitlab {
pub async fn build(api: &Api) -> Result<Self> {
Ok(Self { cfg: api.clone() })
}
}

60
src/serverapi/mod.rs Normal file
View file

@ -0,0 +1,60 @@
use async_trait::async_trait;
use reqwest::ClientBuilder;
use crate::{
config::{ApiType, Config},
error::*,
publish::PublishContext,
release::ReleaseContext,
};
pub mod forgejo;
pub mod gitea;
pub mod github;
pub mod gitlab;
use forgejo::*;
use gitea::*;
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).
// The `async_trait` crate can be used to work around this limitation.
#[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<()>;
}
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?));
}
}
}
for api in collection.iter_mut() {
api.init(cfg).await?;
}
Ok(collection)
}