init
This commit is contained in:
commit
c1c36c7be2
32
.github/workflows/main.yml
vendored
Normal file
32
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
name: Main
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# https://github.com/actions/checkout
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
# https://github.com/actions/docker/tree/master/cli
|
||||||
|
- name: Package
|
||||||
|
uses: actions/docker/cli@master
|
||||||
|
with:
|
||||||
|
args: build -t ${{ github.repository }}:${{ github.sha }} .
|
||||||
|
# https://github.com/actions/docker/tree/master/login
|
||||||
|
- name: Publish Auth
|
||||||
|
uses: actions/docker/login@master
|
||||||
|
env:
|
||||||
|
# https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
# - name: Publish
|
||||||
|
# uses: actions/docker/cli@master
|
||||||
|
# with:
|
||||||
|
# args: push ${{ github.repository }}:${{ github.sha }}
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
1854
Cargo.lock
generated
Normal file
1854
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[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"
|
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# 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 \
|
||||||
|
&& 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"]
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# action gh-release
|
||||||
|
|
||||||
|
A Github Action for creating Github Releases
|
4
rustfmt.toml
Normal file
4
rustfmt.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# 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
|
88
src/github.rs
Normal file
88
src/github.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
use reqwest::{Body, Client};
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Releaser {
|
||||||
|
fn release(
|
||||||
|
&self,
|
||||||
|
github_token: &str,
|
||||||
|
github_repo: &str,
|
||||||
|
release: Release,
|
||||||
|
) -> Result<usize, Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AssetUploader<A: Into<Body> = File> {
|
||||||
|
fn upload(
|
||||||
|
&self,
|
||||||
|
github_token: &str,
|
||||||
|
github_repo: &str,
|
||||||
|
release_id: usize,
|
||||||
|
mime: mime::Mime,
|
||||||
|
asset: A,
|
||||||
|
) -> Result<(), Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ReleaseResponse {
|
||||||
|
id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Releaser for Client {
|
||||||
|
// https://developer.github.com/v3/repos/releases/#create-a-release
|
||||||
|
fn release(
|
||||||
|
&self,
|
||||||
|
github_token: &str,
|
||||||
|
github_repo: &str,
|
||||||
|
release: Release,
|
||||||
|
) -> Result<usize, Box<dyn Error>> {
|
||||||
|
let response: ReleaseResponse = self
|
||||||
|
.post(&format!(
|
||||||
|
"https://api.github.com/repos/{}/releases",
|
||||||
|
github_repo
|
||||||
|
))
|
||||||
|
.header("Authorization", format!("bearer {}", github_token))
|
||||||
|
.json(&release)
|
||||||
|
.send()?
|
||||||
|
.json()?;
|
||||||
|
Ok(response.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
mime: mime::Mime,
|
||||||
|
asset: A,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
self.post(&format!(
|
||||||
|
"http://uploads.github.com/repos/{}/releases/{}",
|
||||||
|
github_repo, release_id
|
||||||
|
))
|
||||||
|
.header("Authorization", format!("bearer {}", github_token))
|
||||||
|
.header("Content-Type", mime.to_string())
|
||||||
|
.body(asset)
|
||||||
|
.send()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {}
|
||||||
|
}
|
84
src/main.rs
Normal file
84
src/main.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
mod github;
|
||||||
|
|
||||||
|
use github::{AssetUploader, Release, Releaser};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{error::Error, fs::File};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default)]
|
||||||
|
struct Config {
|
||||||
|
// provided
|
||||||
|
github_token: String,
|
||||||
|
github_ref: String, // refs/heads/..., ref/tags/...
|
||||||
|
github_repository: String,
|
||||||
|
// optional
|
||||||
|
input_body: Option<String>,
|
||||||
|
input_files: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(conf: &Config) -> Release {
|
||||||
|
let Config {
|
||||||
|
github_ref,
|
||||||
|
input_body,
|
||||||
|
..
|
||||||
|
} = conf;
|
||||||
|
Release {
|
||||||
|
tag_name: github_ref.clone(),
|
||||||
|
body: input_body.clone(),
|
||||||
|
..Release::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
conf: Config,
|
||||||
|
releaser: &dyn Releaser,
|
||||||
|
uploader: &dyn AssetUploader,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
if !conf.github_ref.starts_with("refs/tags/") {
|
||||||
|
log::error!("GH Releases require a tag");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let release_id = releaser.release(
|
||||||
|
conf.github_token.as_str(),
|
||||||
|
conf.github_repository.as_str(),
|
||||||
|
release(&conf),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(patterns) = conf.input_files {
|
||||||
|
for pattern in patterns {
|
||||||
|
for path in glob::glob(pattern.as_str())? {
|
||||||
|
let resolved = path?;
|
||||||
|
let mime =
|
||||||
|
mime_guess::from_path(&resolved).first_or(mime::APPLICATION_OCTET_STREAM);
|
||||||
|
uploader.upload(
|
||||||
|
conf.github_token.as_str(),
|
||||||
|
conf.github_repository.as_str(),
|
||||||
|
release_id,
|
||||||
|
mime,
|
||||||
|
File::open(resolved)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
env_logger::init();
|
||||||
|
let client = Client::new();
|
||||||
|
run(envy::from_env()?, &client, &client)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn release_constructs_a_release_from_a_config() -> Result<(), Box<dyn Error>> {
|
||||||
|
for (conf, expect) in vec![(Config::default(), Release::default())] {
|
||||||
|
assert_eq!(release(&conf), expect);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user