remove previous artifacts
This commit is contained in:
parent
54832a3b39
commit
e60d11aee8
@ -1,3 +0,0 @@
|
|||||||
.github
|
|
||||||
target/rls
|
|
||||||
tests
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
node_modules/
|
node_modules/
|
||||||
__tests__/runner/*
|
__tests__/runner/*
|
||||||
|
1854
Cargo.lock
generated
1854
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "action-gh-release"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["softprops <d.tangren@gmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
mime = "0.3"
|
|
||||||
mime_guess = "2.0"
|
|
||||||
env_logger = "0.6"
|
|
||||||
log = "0.4"
|
|
||||||
glob = "0.3"
|
|
||||||
envy = "0.4"
|
|
||||||
# hack https://docs.rs/openssl/0.10.24/openssl/#vendored
|
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
|
||||||
reqwest = { version = "0.9", features = ["rustls-tls"] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
@ -1,42 +0,0 @@
|
|||||||
# https://hub.docker.com/_/rust?tab=tags
|
|
||||||
FROM rust:1.37.0 as builder
|
|
||||||
|
|
||||||
# musl-gcc
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y \
|
|
||||||
musl-dev \
|
|
||||||
musl-tools \
|
|
||||||
ca-certificates \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl
|
|
||||||
# cache deps https://blog.jawg.io/docker-multi-stage-build/
|
|
||||||
# RUN USER=root cargo init
|
|
||||||
# COPY Cargo.toml .
|
|
||||||
# RUN cargo build --target x86_64-unknown-linux-musl --release
|
|
||||||
# COPY src src
|
|
||||||
COPY . .
|
|
||||||
RUN cargo build --target x86_64-unknown-linux-musl --release
|
|
||||||
RUN strip /app/target/x86_64-unknown-linux-musl/release/action-gh-release
|
|
||||||
|
|
||||||
FROM scratch
|
|
||||||
|
|
||||||
# https://help.github.com/en/articles/metadata-syntax-for-github-actions#about-yaml-syntax-for-github-actions
|
|
||||||
LABEL version="0.1.0" \
|
|
||||||
repository="https://github.com/meetup/action-gh-release/" \
|
|
||||||
homepage="https://github.com/meetup/action-gh-release" \
|
|
||||||
maintainer="Meetup" \
|
|
||||||
"com.github.actions.name"="GH-Release" \
|
|
||||||
"com.github.actions.description"="Creates a new Github Release" \
|
|
||||||
"com.github.actions.icon"="package" \
|
|
||||||
"com.github.actions.color"="green"
|
|
||||||
|
|
||||||
COPY --from=builder \
|
|
||||||
/etc/ssl/certs/ca-certificates.crt \
|
|
||||||
/etc/ssl/certs/
|
|
||||||
COPY --from=builder \
|
|
||||||
/app/target/x86_64-unknown-linux-musl/release/action-gh-release \
|
|
||||||
/action-gh-release
|
|
||||||
ENTRYPOINT ["/action-gh-release"]
|
|
@ -1,4 +0,0 @@
|
|||||||
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_args_layout
|
|
||||||
fn_args_layout = "Vertical"
|
|
||||||
# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports
|
|
||||||
merge_imports = true
|
|
111
src/github.rs
111
src/github.rs
@ -1,111 +0,0 @@
|
|||||||
use mime::Mime;
|
|
||||||
use reqwest::{Body, Client, StatusCode};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{error::Error, fs::File};
|
|
||||||
|
|
||||||
#[derive(Serialize, Default, Debug, PartialEq)]
|
|
||||||
pub struct Release {
|
|
||||||
pub tag_name: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub name: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub body: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub draft: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ReleaseResponse {
|
|
||||||
pub id: usize,
|
|
||||||
pub html_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Releaser {
|
|
||||||
fn release(
|
|
||||||
&self,
|
|
||||||
github_token: &str,
|
|
||||||
github_repo: &str,
|
|
||||||
release: Release,
|
|
||||||
) -> Result<ReleaseResponse, Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AssetUploader<A: Into<Body> = File> {
|
|
||||||
fn upload(
|
|
||||||
&self,
|
|
||||||
github_token: &str,
|
|
||||||
github_repo: &str,
|
|
||||||
release_id: usize,
|
|
||||||
name: &str,
|
|
||||||
mime: Mime,
|
|
||||||
asset: A,
|
|
||||||
) -> Result<StatusCode, Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Releaser for Client {
|
|
||||||
// https://developer.github.com/v3/repos/releases/#create-a-release
|
|
||||||
// https://developer.github.com/v3/repos/releases/#edit-a-release
|
|
||||||
fn release(
|
|
||||||
&self,
|
|
||||||
github_token: &str,
|
|
||||||
github_repo: &str,
|
|
||||||
release: Release,
|
|
||||||
) -> Result<ReleaseResponse, Box<dyn Error>> {
|
|
||||||
let endpoint = format!("https://api.github.com/repos/{}/releases", github_repo);
|
|
||||||
let mut existing = self
|
|
||||||
.get(&format!("{}/tags/{}", endpoint, release.tag_name))
|
|
||||||
.header("Authorization", format!("bearer {}", github_token))
|
|
||||||
.send()?;
|
|
||||||
match existing.status() {
|
|
||||||
StatusCode::NOT_FOUND => Ok(self
|
|
||||||
.post(&format!(
|
|
||||||
"https://api.github.com/repos/{}/releases",
|
|
||||||
github_repo
|
|
||||||
))
|
|
||||||
.header("Authorization", format!("bearer {}", github_token))
|
|
||||||
.json(&release)
|
|
||||||
.send()?
|
|
||||||
.json()?),
|
|
||||||
_ => Ok(self
|
|
||||||
.patch(&format!(
|
|
||||||
"https://api.github.com/repos/{}/releases/{}",
|
|
||||||
github_repo,
|
|
||||||
existing.json::<ReleaseResponse>()?.id
|
|
||||||
))
|
|
||||||
.header("Authorization", format!("bearer {}", github_token))
|
|
||||||
.json(&release)
|
|
||||||
.send()?
|
|
||||||
.json()?),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Into<Body>> AssetUploader<A> for Client {
|
|
||||||
// https://developer.github.com/v3/repos/releases/#upload-a-release-asset
|
|
||||||
fn upload(
|
|
||||||
&self,
|
|
||||||
github_token: &str,
|
|
||||||
github_repo: &str,
|
|
||||||
release_id: usize,
|
|
||||||
name: &str,
|
|
||||||
mime: mime::Mime,
|
|
||||||
asset: A,
|
|
||||||
) -> Result<StatusCode, Box<dyn Error>> {
|
|
||||||
Ok(self
|
|
||||||
.post(&format!(
|
|
||||||
"https://uploads.github.com/repos/{}/releases/{}/assets",
|
|
||||||
github_repo, release_id
|
|
||||||
))
|
|
||||||
.header("Authorization", format!("bearer {}", github_token))
|
|
||||||
.header("Content-Type", mime.to_string())
|
|
||||||
.query(&[("name", name)])
|
|
||||||
.body(asset)
|
|
||||||
.send()?
|
|
||||||
.status())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {}
|
|
||||||
}
|
|
232
src/main.rs
232
src/main.rs
@ -1,232 +0,0 @@
|
|||||||
mod github;
|
|
||||||
|
|
||||||
use github::{AssetUploader, Release, ReleaseResponse, Releaser};
|
|
||||||
use mime::Mime;
|
|
||||||
use reqwest::Client;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{
|
|
||||||
error::Error,
|
|
||||||
ffi::OsStr,
|
|
||||||
fs::{read_to_string, File},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
type BoxError = Box<dyn Error>;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, PartialEq, Clone)]
|
|
||||||
struct Config {
|
|
||||||
// github provided
|
|
||||||
github_token: String,
|
|
||||||
github_ref: String,
|
|
||||||
github_repository: String,
|
|
||||||
// user provided
|
|
||||||
input_name: Option<String>,
|
|
||||||
input_body: Option<String>,
|
|
||||||
input_body_path: Option<PathBuf>,
|
|
||||||
input_files: Option<Vec<String>>,
|
|
||||||
input_draft: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Release> for Config {
|
|
||||||
fn into(self) -> Release {
|
|
||||||
let Config {
|
|
||||||
github_ref,
|
|
||||||
input_name,
|
|
||||||
input_body,
|
|
||||||
input_body_path,
|
|
||||||
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());
|
|
||||||
Release {
|
|
||||||
tag_name,
|
|
||||||
name,
|
|
||||||
body,
|
|
||||||
draft,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_tag<R>(gitref: R) -> bool
|
|
||||||
where
|
|
||||||
R: AsRef<str>,
|
|
||||||
{
|
|
||||||
gitref.as_ref().starts_with("refs/tags/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mime_or_default<P>(path: P) -> Mime
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
mime_guess::from_path(path).first_or(mime::APPLICATION_OCTET_STREAM)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paths<P>(
|
|
||||||
patterns: impl IntoIterator<Item = P>
|
|
||||||
) -> 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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
conf: Config,
|
|
||||||
releaser: &dyn Releaser,
|
|
||||||
uploader: &dyn AssetUploader,
|
|
||||||
) -> Result<(), BoxError> {
|
|
||||||
if !is_tag(&conf.github_ref) {
|
|
||||||
eprintln!("⚠️ GitHub Releases requires a tag");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let ReleaseResponse { id, html_url } = releaser.release(
|
|
||||||
conf.github_token.as_str(),
|
|
||||||
conf.github_repository.as_str(),
|
|
||||||
conf.clone().into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(patterns) = conf.input_files {
|
|
||||||
for path in paths(patterns)? {
|
|
||||||
let name = &path
|
|
||||||
.file_name()
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.unwrap_or_else(|| "UnknownFile");
|
|
||||||
println!("⬆️ Uploading {}...", name);
|
|
||||||
let status = uploader.upload(
|
|
||||||
conf.github_token.as_str(),
|
|
||||||
conf.github_repository.as_str(),
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
mime_or_default(&path),
|
|
||||||
File::open(&path)?,
|
|
||||||
)?;
|
|
||||||
if !status.is_success() {
|
|
||||||
println!("⚠️ Failed uploading {} with error {}", name, status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("🎉 Release ready at {}", html_url);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), BoxError> {
|
|
||||||
env_logger::init();
|
|
||||||
let client = Client::new();
|
|
||||||
run(envy::from_env()?, &client, &client)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn mime_or_default_defaults_to_octect_stream() {
|
|
||||||
assert_eq!(
|
|
||||||
mime_or_default("umbiguous-file"),
|
|
||||||
mime::APPLICATION_OCTET_STREAM
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
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()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
] {
|
|
||||||
assert_eq!(expect, conf.into());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn is_tag_checks_refs() {
|
|
||||||
for (gitref, expect) in &[("refs/tags/foo", true), ("refs/heads/master", false)] {
|
|
||||||
assert_eq!(is_tag(gitref), *expect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn paths_resolves_pattern_to_file_paths() -> Result<(), BoxError> {
|
|
||||||
assert_eq!(paths(vec!["tests/data/**/*"])?.into_iter().count(), 1);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn config_is_parsed_from_env() -> Result<(), BoxError> {
|
|
||||||
for (env, expect) in vec![(
|
|
||||||
vec![
|
|
||||||
("GITHUB_TOKEN".into(), "123".into()),
|
|
||||||
("GITHUB_REF".into(), "refs/tags/v1.0.0".into()),
|
|
||||||
("GITHUB_REPOSITORY".into(), "foo/bar".into()),
|
|
||||||
("INPUT_NAME".into(), "test release".into()),
|
|
||||||
("INPUT_BODY".into(), ":)".into()),
|
|
||||||
("INPUT_FILES".into(), "*.md".into()),
|
|
||||||
("INPUT_DRAFT".into(), "true".into()),
|
|
||||||
("INPUT_BODY_PATH".into(), "tests/data/foo/bar.txt".into()),
|
|
||||||
],
|
|
||||||
Config {
|
|
||||||
github_token: "123".into(),
|
|
||||||
github_ref: "refs/tags/v1.0.0".into(),
|
|
||||||
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()),
|
|
||||||
input_files: Some(vec!["*.md".into()]),
|
|
||||||
input_draft: Some(true),
|
|
||||||
},
|
|
||||||
)] {
|
|
||||||
assert_eq!(expect, envy::from_iter::<_, Config>(env)?)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user