forked from mirrors/action-gh-release
		
	init
This commit is contained in:
		
						commit
						c1c36c7be2
					
				
					 9 changed files with 2129 additions and 0 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue