2024-04-25 14:33:48 +02:00
|
|
|
use std::str::FromStr;
|
2024-02-25 00:18:12 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap, fmt::Debug, fs::File, io::BufReader, path::PathBuf, process::Command,
|
|
|
|
};
|
2024-01-24 23:04:01 +01:00
|
|
|
|
|
|
|
use git2;
|
2024-01-25 22:33:48 +01:00
|
|
|
use libpt::log::{debug, error, trace};
|
2024-04-25 14:33:48 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-01-25 22:08:05 +01:00
|
|
|
use url::Url;
|
2024-01-24 23:04:01 +01:00
|
|
|
|
2024-01-25 22:08:05 +01:00
|
|
|
use crate::error::*;
|
2024-01-24 23:04:01 +01:00
|
|
|
|
|
|
|
pub mod cli;
|
2024-02-19 22:07:51 +01:00
|
|
|
pub mod packages;
|
2024-01-25 22:08:05 +01:00
|
|
|
use cli::Cli;
|
2024-01-24 23:04:01 +01:00
|
|
|
|
2024-01-27 23:26:49 +01:00
|
|
|
pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> {
|
|
|
|
fn check(&self) -> Result<()>;
|
|
|
|
}
|
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-24 23:04:01 +01:00
|
|
|
pub struct Changelog {
|
2024-01-27 23:26:49 +01:00
|
|
|
pub enable: bool,
|
2024-01-25 21:37:52 +00:00
|
|
|
#[serde(alias = "git-log")]
|
2024-01-27 23:26:49 +01:00
|
|
|
pub git_log: bool,
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
2024-01-27 23:26:49 +01:00
|
|
|
impl YamlConfigSection for Changelog {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-27 23:26:49 +01:00
|
|
|
pub struct UseCargo {
|
2024-01-27 23:46:00 +01:00
|
|
|
pub publish: bool,
|
|
|
|
pub registries: Vec<String>,
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
2024-01-27 23:26:49 +01:00
|
|
|
impl YamlConfigSection for UseCargo {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2024-01-24 23:04:01 +01:00
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-27 23:26:49 +01:00
|
|
|
pub struct Uses {
|
|
|
|
cargo: UseCargo,
|
|
|
|
}
|
|
|
|
impl YamlConfigSection for Uses {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
self.cargo.check()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-01-25 22:33:48 +01:00
|
|
|
}
|
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-02-25 00:15:11 +01:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2024-02-24 13:24:14 +01:00
|
|
|
pub enum Pass {
|
|
|
|
/// pass specified as plainext
|
2024-02-25 00:15:11 +01:00
|
|
|
#[serde(alias = "pass_text")]
|
2024-02-24 13:24:14 +01:00
|
|
|
Text(String),
|
|
|
|
/// pass to be loaded from an env var
|
2024-02-25 00:15:11 +01:00
|
|
|
#[serde(alias = "pass_env")]
|
2024-02-24 13:24:14 +01:00
|
|
|
Env(String),
|
|
|
|
/// pass to be loaded from a file
|
2024-02-25 00:15:11 +01:00
|
|
|
#[serde(alias = "pass_file")]
|
2024-02-24 13:24:14 +01:00
|
|
|
File(PathBuf),
|
|
|
|
}
|
|
|
|
impl Pass {
|
|
|
|
/// Get the pass, extracting from the underlying source
|
2024-02-24 14:10:44 +01:00
|
|
|
pub fn get_pass(&self) -> Result<String> {
|
2024-02-24 13:24:14 +01:00
|
|
|
self.check()?;
|
|
|
|
Ok(match self {
|
|
|
|
Self::Text(pass) => pass.clone(),
|
2024-02-24 12:28:29 +00:00
|
|
|
Self::Env(key) => std::env::var(key).map_err(ConfigError::from)?,
|
2024-02-24 13:24:14 +01:00
|
|
|
Self::File(file) => std::fs::read_to_string(file)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl YamlConfigSection for Pass {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
match self {
|
|
|
|
Self::Text(_) => (),
|
|
|
|
Self::Env(envvar) => {
|
2024-02-24 12:28:29 +00:00
|
|
|
if !std::env::var(envvar).map_err(ConfigError::from)?.is_empty() {
|
2024-02-24 13:24:14 +01:00
|
|
|
} else {
|
|
|
|
return Err(ConfigError::EnvNotSet(envvar.clone()).into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self::File(file) => {
|
|
|
|
if !file.exists() {
|
|
|
|
return Err(ConfigError::PassFileDoesNotExist(file.clone()).into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-27 23:26:49 +01:00
|
|
|
pub struct ApiAuth {
|
|
|
|
pub user: String,
|
2024-02-24 13:24:14 +01:00
|
|
|
pub pass: Pass,
|
2024-01-27 23:26:49 +01:00
|
|
|
}
|
|
|
|
impl YamlConfigSection for ApiAuth {
|
|
|
|
fn check(&self) -> Result<()> {
|
2024-02-24 13:24:14 +01:00
|
|
|
self.pass.check()?;
|
2024-01-25 22:33:48 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2024-01-25 22:08:05 +01:00
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-24 23:04:01 +01:00
|
|
|
pub struct Api {
|
2024-02-25 00:15:11 +01:00
|
|
|
#[serde(alias = "type")]
|
2024-02-16 20:00:55 +01:00
|
|
|
pub server_type: ApiType,
|
2024-04-25 14:33:48 +02:00
|
|
|
/// May be left empty if the [ApiType] is [Github](ApiType::Github).
|
|
|
|
pub endpoint: Option<Url>,
|
2024-01-25 22:33:48 +01:00
|
|
|
/// May be left empty if the Api does not need auth or the auth is part of the
|
|
|
|
/// [endpoint](Api::endpoint) [Url].
|
2024-01-27 23:26:49 +01:00
|
|
|
pub auth: Option<ApiAuth>,
|
2024-02-25 00:01:03 +01:00
|
|
|
/// Name of the repository on the Git server, as git itself has no concept of repository name
|
2024-02-25 00:15:11 +01:00
|
|
|
pub repository: String,
|
2024-01-25 22:33:48 +01:00
|
|
|
}
|
2024-01-27 23:26:49 +01:00
|
|
|
impl YamlConfigSection for Api {
|
|
|
|
fn check(&self) -> Result<()> {
|
2024-02-16 20:00:55 +01:00
|
|
|
self.server_type.check()?;
|
2024-04-25 14:33:48 +02:00
|
|
|
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());
|
2024-01-27 23:26:49 +01:00
|
|
|
}
|
|
|
|
if self.auth.is_some() {
|
|
|
|
self.auth.clone().unwrap().check()?;
|
2024-01-25 22:33:48 +01:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
2024-01-24 23:04:01 +01:00
|
|
|
pub enum ApiType {
|
2024-01-25 22:33:48 +01:00
|
|
|
#[serde(alias = "gitea")]
|
2024-01-24 23:04:01 +01:00
|
|
|
Gitea,
|
2024-01-25 22:33:48 +01:00
|
|
|
#[serde(alias = "gitlab")]
|
2024-01-24 23:04:01 +01:00
|
|
|
Gitlab,
|
2024-01-25 22:33:48 +01:00
|
|
|
#[serde(alias = "github", alias = "GitHub")]
|
2024-01-24 23:04:01 +01:00
|
|
|
Github,
|
2024-02-19 22:07:51 +01:00
|
|
|
#[serde(alias = "forgejo")]
|
|
|
|
Forgejo,
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
2024-04-25 14:33:48 +02:00
|
|
|
impl ApiType {
|
|
|
|
pub fn default_endpoint(&self) -> Option<Url> {
|
|
|
|
match self {
|
|
|
|
Self::Github => Some(Url::from_str("https://github.com").unwrap()),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 23:26:49 +01:00
|
|
|
impl YamlConfigSection for ApiType {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2024-01-24 23:04:01 +01:00
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-02-25 01:16:19 +01:00
|
|
|
#[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) => {
|
2024-02-25 00:18:12 +00:00
|
|
|
match Command::new("/bin/bash")
|
|
|
|
.arg("-c")
|
|
|
|
.arg(shell_command)
|
|
|
|
.output()
|
|
|
|
{
|
2024-02-25 01:16:19 +01:00
|
|
|
Ok(output) => {
|
|
|
|
// TODO: check status
|
|
|
|
String::from_utf8(output.stdout).unwrap()
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
panic!("{err:?}");
|
|
|
|
}
|
|
|
|
}
|
2024-02-25 00:18:12 +00:00
|
|
|
}
|
2024-04-25 15:44:55 +02:00
|
|
|
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:?}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
2024-02-25 01:16:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-25 14:33:48 +02:00
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
2024-01-24 23:04:01 +01:00
|
|
|
pub struct YamlConfig {
|
|
|
|
pub changelog: Changelog,
|
|
|
|
pub uses: Uses,
|
2024-01-25 22:33:48 +01:00
|
|
|
pub api: HashMap<String, Api>,
|
2024-02-25 01:16:19 +01:00
|
|
|
pub version: Version,
|
2024-01-25 22:33:48 +01:00
|
|
|
}
|
2024-01-27 23:26:49 +01:00
|
|
|
impl YamlConfigSection for YamlConfig {
|
|
|
|
fn check(&self) -> Result<()> {
|
|
|
|
self.changelog.check()?;
|
|
|
|
self.uses.check()?;
|
2024-02-25 01:16:19 +01:00
|
|
|
self.version.check()?;
|
2024-01-27 23:26:49 +01:00
|
|
|
for api in self.api.values() {
|
|
|
|
api.check()?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2024-01-25 22:33:48 +01:00
|
|
|
|
|
|
|
impl YamlConfig {
|
|
|
|
/// check if the built configuration is valid
|
|
|
|
pub fn check(&self) -> Result<()> {
|
|
|
|
for api in &self.api {
|
|
|
|
api.1.check()?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Config {
|
|
|
|
pub yaml: YamlConfig,
|
2024-01-27 23:26:49 +01:00
|
|
|
pub cli: Cli,
|
2024-01-24 23:04:01 +01:00
|
|
|
pub repo: git2::Repository,
|
|
|
|
pub path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for Config {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}",
|
2024-02-16 18:37:39 +01:00
|
|
|
format_args!(
|
2024-01-24 23:04:01 +01:00
|
|
|
"Config {{yaml: {:?}, repo_path: {:?}}}",
|
|
|
|
self.yaml, self.path
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
2024-02-16 18:58:20 +01:00
|
|
|
pub fn load(cli: &Cli) -> Result<Self> {
|
2024-01-24 23:04:01 +01:00
|
|
|
let repo = match git2::Repository::open_from_env() {
|
|
|
|
Ok(repo) => repo,
|
2024-01-25 21:37:52 +00:00
|
|
|
Err(_err) => {
|
2024-01-25 22:33:48 +01:00
|
|
|
let err = ConfigError::GitRepoNotFound.into();
|
2024-01-25 22:08:05 +01:00
|
|
|
error!("{err}");
|
|
|
|
return Err(err);
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let mut path = repo.path().to_path_buf();
|
|
|
|
path.pop(); // we want the real root, not the `.git` dir
|
|
|
|
|
2024-01-25 22:08:05 +01:00
|
|
|
let yaml_file_name = if path.join(".autocrate.yaml").exists() {
|
|
|
|
".autocrate.yaml"
|
|
|
|
} else if path.join(".autocrate.yml").exists() {
|
|
|
|
".autocrate.yml"
|
|
|
|
} else {
|
2024-01-25 22:33:48 +01:00
|
|
|
let err = ConfigError::NoYamlFile.into();
|
2024-01-25 22:08:05 +01:00
|
|
|
error!("{err}");
|
|
|
|
return Err(err);
|
|
|
|
};
|
|
|
|
let yaml_file_path = path.join(yaml_file_name);
|
|
|
|
// we can be sure it exists from the checks above
|
|
|
|
assert!(yaml_file_path.exists());
|
|
|
|
if !yaml_file_path.is_file() {
|
2024-01-25 22:33:48 +01:00
|
|
|
let err = ConfigError::YamlFileIsNotFile.into();
|
2024-01-25 22:08:05 +01:00
|
|
|
error!("{err}");
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
let yaml_rd = BufReader::new(File::open(yaml_file_path)?);
|
2024-01-25 22:33:48 +01:00
|
|
|
debug!("reading yaml config and building data structure");
|
2024-01-25 22:08:05 +01:00
|
|
|
let yaml: YamlConfig = serde_yaml::from_reader(yaml_rd)?;
|
2024-01-25 22:33:48 +01:00
|
|
|
trace!("load config:\n{:#?}", yaml);
|
|
|
|
yaml.check()?;
|
|
|
|
debug!("built and checked yaml config");
|
2024-01-25 22:08:05 +01:00
|
|
|
|
2024-01-27 23:26:49 +01:00
|
|
|
Ok(Config {
|
|
|
|
yaml,
|
|
|
|
repo,
|
|
|
|
path,
|
2024-02-16 18:58:20 +01:00
|
|
|
cli: cli.clone(),
|
2024-01-27 23:26:49 +01:00
|
|
|
})
|
2024-01-24 23:04:01 +01:00
|
|
|
}
|
|
|
|
}
|