Files
action-gh-release/src/main.rs
T

233 lines
6.5 KiB
Rust
Raw Normal View History

2019-08-25 02:13:05 -04:00
mod github;
2019-08-25 12:44:14 -04:00
use github::{AssetUploader, Release, ReleaseResponse, Releaser};
2019-08-25 18:37:53 -04:00
use mime::Mime;
2019-08-25 02:13:05 -04:00
use reqwest::Client;
use serde::Deserialize;
2019-08-25 12:37:35 -04:00
use std::{
error::Error,
2019-08-25 23:33:39 -04:00
ffi::OsStr,
fs::{read_to_string, File},
2019-08-25 12:37:35 -04:00
path::{Path, PathBuf},
};
2019-08-25 02:13:05 -04:00
2019-08-25 18:37:53 -04:00
type BoxError = Box<dyn Error>;
2019-08-25 22:41:08 -04:00
#[derive(Deserialize, Default, Debug, PartialEq, Clone)]
2019-08-25 02:13:05 -04:00
struct Config {
2019-08-25 22:08:43 -04:00
// github provided
2019-08-25 02:13:05 -04:00
github_token: String,
2019-08-25 22:08:43 -04:00
github_ref: String,
2019-08-25 02:13:05 -04:00
github_repository: String,
2019-08-25 22:08:43 -04:00
// user provided
2019-08-25 18:37:53 -04:00
input_name: Option<String>,
2019-08-25 02:13:05 -04:00
input_body: Option<String>,
input_body_path: Option<PathBuf>,
2019-08-25 02:13:05 -04:00
input_files: Option<Vec<String>>,
2019-08-25 22:08:43 -04:00
input_draft: Option<bool>,
2019-08-25 02:13:05 -04:00
}
2019-08-25 22:41:08 -04:00
impl Into<Release> for Config {
fn into(self) -> Release {
let Config {
github_ref,
input_name,
input_body,
input_body_path,
2019-08-25 22:41:08 -04:00
input_draft,
..
} = self;
let tag_name = github_ref.trim_start_matches("refs/tags/").to_string();
let name = input_name.clone().or_else(|| Some(tag_name.clone()));
let draft = input_draft;
let body = input_body_path
.and_then(|path| read_to_string(path).ok())
.or_else(|| input_body.clone());
2019-08-25 22:41:08 -04:00
Release {
tag_name,
name,
body,
2019-08-25 22:41:08 -04:00
draft,
}
2019-08-25 02:13:05 -04:00
}
}
2019-08-25 02:27:52 -04:00
fn is_tag<R>(gitref: R) -> bool
where
R: AsRef<str>,
{
2019-08-25 02:23:48 -04:00
gitref.as_ref().starts_with("refs/tags/")
}
2019-08-25 18:37:53 -04:00
fn mime_or_default<P>(path: P) -> Mime
2019-08-25 02:27:52 -04:00
where
P: AsRef<Path>,
{
mime_guess::from_path(path).first_or(mime::APPLICATION_OCTET_STREAM)
}
fn paths<P>(
patterns: impl IntoIterator<Item = P>
2019-08-25 18:37:53 -04:00
) -> Result<impl IntoIterator<Item = PathBuf>, BoxError>
where
P: AsRef<str>,
{
patterns
.into_iter()
.try_fold(Vec::new(), |mut paths, pattern| {
let matched = glob::glob(pattern.as_ref())?
.filter_map(Result::ok)
.filter(|p| p.is_file());
paths.extend(matched);
Ok(paths)
})
}
2019-08-25 02:13:05 -04:00
fn run(
conf: Config,
releaser: &dyn Releaser,
uploader: &dyn AssetUploader,
2019-08-25 18:37:53 -04:00
) -> Result<(), BoxError> {
2019-08-25 02:23:48 -04:00
if !is_tag(&conf.github_ref) {
2019-08-26 00:25:15 -04:00
eprintln!("⚠️ GitHub Releases requires a tag");
2019-08-25 02:13:05 -04:00
return Ok(());
}
2019-08-25 12:44:14 -04:00
let ReleaseResponse { id, html_url } = releaser.release(
2019-08-25 02:13:05 -04:00
conf.github_token.as_str(),
conf.github_repository.as_str(),
2019-08-25 22:41:08 -04:00
conf.clone().into(),
2019-08-25 02:13:05 -04:00
)?;
if let Some(patterns) = conf.input_files {
for path in paths(patterns)? {
2019-08-26 00:40:00 -04:00
let name = &path
.file_name()
.and_then(OsStr::to_str)
.unwrap_or_else(|| "UnknownFile");
println!("⬆️ Uploading {}...", name);
2019-08-25 21:59:02 -04:00
let status = uploader.upload(
2019-08-25 12:37:35 -04:00
conf.github_token.as_str(),
conf.github_repository.as_str(),
2019-08-25 12:44:14 -04:00
id,
2019-08-26 00:40:00 -04:00
name,
2019-08-25 12:37:35 -04:00
mime_or_default(&path),
2019-08-25 23:33:39 -04:00
File::open(&path)?,
2019-08-25 12:37:35 -04:00
)?;
2019-08-26 00:40:00 -04:00
if !status.is_success() {
println!("⚠️ Failed uploading {} with error {}", name, status);
}
2019-08-25 02:13:05 -04:00
}
}
2019-08-25 21:59:02 -04:00
println!("🎉 Release ready at {}", html_url);
2019-08-25 02:13:05 -04:00
Ok(())
}
2019-08-25 18:37:53 -04:00
fn main() -> Result<(), BoxError> {
2019-08-25 02:13:05 -04:00
env_logger::init();
let client = Client::new();
run(envy::from_env()?, &client, &client)
}
#[cfg(test)]
mod tests {
use super::*;
2019-08-25 02:27:52 -04:00
#[test]
fn mime_or_default_defaults_to_octect_stream() {
assert_eq!(
mime_or_default("umbiguous-file"),
mime::APPLICATION_OCTET_STREAM
)
}
2019-08-25 02:13:05 -04:00
#[test]
2019-08-25 18:37:53 -04:00
fn release_constructs_a_release_from_a_config() -> Result<(), BoxError> {
for (conf, expect) in vec![
(
Config {
github_ref: "refs/tags/v1.0.0".into(),
..Config::default()
},
Release {
tag_name: "v1.0.0".into(),
name: Some("v1.0.0".into()),
..Release::default()
},
),
(
Config {
github_ref: "refs/tags/v1.0.0".into(),
input_name: Some("custom".into()),
..Config::default()
},
Release {
tag_name: "v1.0.0".into(),
name: Some("custom".into()),
..Release::default()
},
),
(
Config {
github_ref: "refs/tags/v1.0.0".into(),
input_body: Some("fallback".into()),
input_body_path: Some("tests/data/foo/bar.txt".into()),
..Config::default()
},
Release {
tag_name: "v1.0.0".into(),
name: Some("v1.0.0".into()),
body: Some("release me".into()),
..Release::default()
},
),
2019-08-25 18:37:53 -04:00
] {
2019-08-25 22:41:08 -04:00
assert_eq!(expect, conf.into());
2019-08-25 02:13:05 -04:00
}
Ok(())
}
2019-08-25 02:23:48 -04:00
#[test]
fn is_tag_checks_refs() {
2019-08-25 02:27:52 -04:00
for (gitref, expect) in &[("refs/tags/foo", true), ("refs/heads/master", false)] {
2019-08-25 02:23:48 -04:00
assert_eq!(is_tag(gitref), *expect)
}
}
#[test]
2019-08-25 18:37:53 -04:00
fn paths_resolves_pattern_to_file_paths() -> Result<(), BoxError> {
assert_eq!(paths(vec!["tests/data/**/*"])?.into_iter().count(), 1);
Ok(())
}
2019-08-25 19:45:44 -04:00
#[test]
fn config_is_parsed_from_env() -> Result<(), BoxError> {
for (env, expect) in vec![(
vec![
("GITHUB_TOKEN".into(), "123".into()),
2019-08-26 00:43:16 -04:00
("GITHUB_REF".into(), "refs/tags/v1.0.0".into()),
2019-08-25 19:45:44 -04:00
("GITHUB_REPOSITORY".into(), "foo/bar".into()),
("INPUT_NAME".into(), "test release".into()),
("INPUT_BODY".into(), ":)".into()),
("INPUT_FILES".into(), "*.md".into()),
2019-08-25 22:41:08 -04:00
("INPUT_DRAFT".into(), "true".into()),
("INPUT_BODY_PATH".into(), "tests/data/foo/bar.txt".into()),
2019-08-25 19:45:44 -04:00
],
Config {
github_token: "123".into(),
2019-08-26 00:43:16 -04:00
github_ref: "refs/tags/v1.0.0".into(),
2019-08-25 19:45:44 -04:00
github_repository: "foo/bar".into(),
input_name: Some("test release".into()),
input_body: Some(":)".into()),
input_body_path: Some("tests/data/foo/bar.txt".into()),
2019-08-25 21:59:02 -04:00
input_files: Some(vec!["*.md".into()]),
2019-08-25 22:41:08 -04:00
input_draft: Some(true),
2019-08-25 19:45:44 -04:00
},
)] {
assert_eq!(expect, envy::from_iter::<_, Config>(env)?)
}
Ok(())
}
2019-08-25 02:13:05 -04:00
}