generated from PlexSheep/rs-base
Compare commits
24 commits
499bcdd4c1
...
6f70057aba
Author | SHA1 | Date | |
---|---|---|---|
6f70057aba | |||
|
a42393864e | ||
3e0d32721b | |||
45ba3f6b97 | |||
39538b7b01 | |||
d02830b656 | |||
|
19d5d11b05 | ||
bd04eea9cc | |||
f12f5763d1 | |||
77f0aabdfc | |||
|
0abcdff5af | ||
acc5381dd1 | |||
1c965da645 | |||
b2bd3a9dde | |||
0f1b82c4c9 | |||
|
48008c4d7a | ||
2568d96ac3 | |||
660d4bd9da | |||
c5c79b784a | |||
|
db14cb7a8f | ||
02c51d6e4a | |||
8c9e893d44 | |||
0f6ca5d6f2 | |||
6267b0af45 |
20 changed files with 447 additions and 70 deletions
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
12
.github/workflows/cargo.yaml
vendored
12
.github/workflows/cargo.yaml
vendored
|
@ -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
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
41
README.md
41
README.md
|
@ -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).
|
||||||
|
|
||||||
|
|
|
@ -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' \
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
3
src/config/packages.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub enum PackageType {
|
||||||
|
Cargo,
|
||||||
|
}
|
18
src/error.rs
18
src/error.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -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
7
src/publish/mod.rs
Normal 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
31
src/release/mod.rs
Normal 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
83
src/serverapi/forgejo.rs
Normal 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
32
src/serverapi/gitea.rs
Normal 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
32
src/serverapi/github.rs
Normal 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
32
src/serverapi/gitlab.rs
Normal 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
60
src/serverapi/mod.rs
Normal 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)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue