mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 04:09:09 +00:00
init: basic github actions impl in rust
This commit is contained in:
parent
3474131750
commit
cc1a388eef
9 changed files with 1407 additions and 0 deletions
5
package/action/.gitignore
vendored
Normal file
5
package/action/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Cargo
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Downloaded files
|
||||||
|
lune-*
|
5
package/action/.vscode/settings.json
vendored
Normal file
5
package/action/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"./Cargo.toml"
|
||||||
|
]
|
||||||
|
}
|
1065
package/action/Cargo.lock
generated
Normal file
1065
package/action/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
package/action/Cargo.toml
Normal file
18
package/action/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "action"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actions-core = "0.0.2"
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
better-panic = "0.3.0"
|
||||||
|
directories = "5.0.1"
|
||||||
|
home = "0.5.5"
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
ureq = "2.7.1"
|
||||||
|
url = "2.4.1"
|
||||||
|
zip = "0.6.6"
|
116
package/action/src/download.rs
Normal file
116
package/action/src/download.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
fs::File,
|
||||||
|
io::{Error, ErrorKind, Read, Seek, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use directories::BaseDirs;
|
||||||
|
use url::Url;
|
||||||
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
github::{FetchResult, Fetcher, GitHub, Method},
|
||||||
|
types::{LuneReleaseData, LuneReleaseFetched},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parse_release_data(data: &LuneReleaseData) -> LuneReleaseFetched {
|
||||||
|
let unknown = &String::from("UNKNOWN");
|
||||||
|
|
||||||
|
let mut version = unknown.to_owned();
|
||||||
|
let mut download = unknown.to_owned();
|
||||||
|
let mut artifact_name = unknown.to_owned();
|
||||||
|
|
||||||
|
for asset in &data.assets {
|
||||||
|
let parts = asset
|
||||||
|
.name
|
||||||
|
.split("-")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
// [0] => always "lune"
|
||||||
|
// [1] => version
|
||||||
|
// [2] => platform
|
||||||
|
// [3] => arch .. ".zip"
|
||||||
|
|
||||||
|
if parts[2] == env::consts::OS && parts[3].trim_end_matches(".zip") == env::consts::ARCH {
|
||||||
|
version = (&parts[1]).to_owned();
|
||||||
|
download = (&asset.browser_download_url).to_owned();
|
||||||
|
artifact_name = (&asset.name).to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LuneReleaseFetched {
|
||||||
|
version,
|
||||||
|
download,
|
||||||
|
artifact_name,
|
||||||
|
raw: data.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download_release() -> Result<(PathBuf, LuneReleaseFetched)> {
|
||||||
|
let parsed_release_data =
|
||||||
|
parse_release_data(&GitHub::new(("filiptibell", "lune")).fetch_releases()?);
|
||||||
|
|
||||||
|
let fetcher = Fetcher::new();
|
||||||
|
let resp = fetcher.fetch::<_, ()>(
|
||||||
|
Method::Get,
|
||||||
|
Url::from_str(&parsed_release_data.download.as_str())?,
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match resp {
|
||||||
|
FetchResult::Response(res) => {
|
||||||
|
let mut zip_archive = File::create(&parsed_release_data.artifact_name)?;
|
||||||
|
|
||||||
|
let mut bytes = res
|
||||||
|
.into_reader()
|
||||||
|
.bytes()
|
||||||
|
.map(|elem| elem.unwrap())
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
|
||||||
|
zip_archive.write_all(&mut bytes)?
|
||||||
|
}
|
||||||
|
FetchResult::Result(_) => unreachable!("Fetcher returned Result instead of Response"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
PathBuf::from(&parsed_release_data.artifact_name),
|
||||||
|
parsed_release_data,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_lune(lune_archive_reader: impl Read + Seek, meta: LuneReleaseFetched) -> Result<()> {
|
||||||
|
let dir_name = meta.artifact_name.trim_end_matches(".zip");
|
||||||
|
|
||||||
|
let mut lune_zip = ZipArchive::new(lune_archive_reader)?;
|
||||||
|
lune_zip.extract(dir_name)?;
|
||||||
|
|
||||||
|
let bin_name = match env::consts::OS {
|
||||||
|
"windows" => "lune.exe",
|
||||||
|
"macos" | "linux" => "lune",
|
||||||
|
_ => panic!("unsupported platform"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bin_base_dir = BaseDirs::new()
|
||||||
|
.ok_or(Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
"failed to create BaseDirs instance",
|
||||||
|
))?
|
||||||
|
.home_dir()
|
||||||
|
.join("bin");
|
||||||
|
|
||||||
|
if !bin_base_dir.exists() {
|
||||||
|
std::fs::create_dir(&bin_base_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: macOS and windows support
|
||||||
|
let lune_bin_path = bin_base_dir.join(bin_name);
|
||||||
|
|
||||||
|
File::create(&lune_bin_path)?;
|
||||||
|
std::fs::rename(PathBuf::from(dir_name).join(bin_name), lune_bin_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
153
package/action/src/github.rs
Normal file
153
package/action/src/github.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
use std::{collections::HashMap, str::FromStr, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
|
use ureq::Response;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::types::LuneReleaseData;
|
||||||
|
|
||||||
|
pub struct GitHub {
|
||||||
|
fetcher: Fetcher,
|
||||||
|
repository: Repository,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitHub {
|
||||||
|
pub fn new(repo: (&str, &str)) -> Self {
|
||||||
|
let fetcher = Fetcher::new();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fetcher: (&fetcher).to_owned(),
|
||||||
|
repository: Repository::new(repo.0, repo.1, fetcher),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_releases(&self) -> Result<LuneReleaseData> {
|
||||||
|
let api_uri = Url::from_str(self.repository.api_url().as_str())?
|
||||||
|
.join("releases/")?
|
||||||
|
.join("latest")?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
match self
|
||||||
|
.fetcher
|
||||||
|
.fetch::<_, LuneReleaseData>(Method::Get, api_uri, true)?
|
||||||
|
{
|
||||||
|
FetchResult::Result(res) => res,
|
||||||
|
FetchResult::Response(_) => {
|
||||||
|
unreachable!("Fetcher returned Response instead of Result")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Repository {
|
||||||
|
fetcher: Fetcher,
|
||||||
|
scope: String,
|
||||||
|
repo: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct RepositoryMeta {
|
||||||
|
pub full_name: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub license: HashMap<String, String>,
|
||||||
|
pub topics: Vec<String>,
|
||||||
|
pub forks: u32,
|
||||||
|
pub open_issues: u32,
|
||||||
|
pub stars: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repository {
|
||||||
|
pub fn new<T>(scope: T, repo: T, fetcher: Fetcher) -> Self
|
||||||
|
where
|
||||||
|
T: ToString,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
fetcher,
|
||||||
|
scope: scope.to_string(),
|
||||||
|
repo: repo.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope(&self) -> &String {
|
||||||
|
return &self.scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repo(&self) -> &String {
|
||||||
|
return &self.repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn api_url(&self) -> String {
|
||||||
|
return format!("https://api.github.com/repos/{}/{}/", self.scope, self.repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_meta(&self) -> Result<RepositoryMeta> {
|
||||||
|
Ok(
|
||||||
|
match self.fetcher.fetch::<_, RepositoryMeta>(
|
||||||
|
Method::Get,
|
||||||
|
Url::from_str(self.api_url().as_str())?,
|
||||||
|
true,
|
||||||
|
)? {
|
||||||
|
FetchResult::Result(deserialized) => deserialized,
|
||||||
|
FetchResult::Response(_) => {
|
||||||
|
unreachable!("Fetcher returned Response instead of Result")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Fetcher {
|
||||||
|
client: ureq::Agent,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Method {
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FetchResult<D> {
|
||||||
|
Result(D),
|
||||||
|
Response(Response),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fetcher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
client: ureq::builder()
|
||||||
|
.redirects(2)
|
||||||
|
.https_only(true)
|
||||||
|
.timeout(Duration::from_secs(30))
|
||||||
|
.user_agent("lune-action/0.1.0")
|
||||||
|
.build(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch<U, D>(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
uri: U,
|
||||||
|
to_deserialize: bool,
|
||||||
|
) -> Result<FetchResult<D>>
|
||||||
|
where
|
||||||
|
U: Into<url::Url>,
|
||||||
|
D: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let method = match method {
|
||||||
|
Method::Get => "GET",
|
||||||
|
Method::Post => "POST",
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = self.client.request_url(method, &uri.into());
|
||||||
|
|
||||||
|
Ok(match to_deserialize {
|
||||||
|
true => {
|
||||||
|
FetchResult::Result(serde_json::from_reader::<_, D>(req.call()?.into_reader())?)
|
||||||
|
}
|
||||||
|
false => FetchResult::Response(req.call()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
package/action/src/lib.rs
Normal file
3
package/action/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod types;
|
||||||
|
pub mod github;
|
||||||
|
pub mod download;
|
21
package/action/src/main.rs
Normal file
21
package/action/src/main.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use action::download::{download_release, install_lune};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
better_panic::install();
|
||||||
|
|
||||||
|
let (zip_path, meta) = download_release().expect("failed to download latest lune release");
|
||||||
|
|
||||||
|
install_lune(
|
||||||
|
File::open(&zip_path).expect(
|
||||||
|
format!(
|
||||||
|
"failed to open downloaded lune release zip file @ {}",
|
||||||
|
zip_path.to_string_lossy().to_string()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
),
|
||||||
|
meta,
|
||||||
|
)
|
||||||
|
.expect("failed to install lune. did we not have perms to write to the required directories?");
|
||||||
|
}
|
21
package/action/src/types.rs
Normal file
21
package/action/src/types.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct LuneReleaseData {
|
||||||
|
pub name: String,
|
||||||
|
pub assets: Vec<LuneAssetData>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct LuneAssetData {
|
||||||
|
pub browser_download_url: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct LuneReleaseFetched {
|
||||||
|
pub version: String,
|
||||||
|
pub download: String,
|
||||||
|
pub artifact_name: String,
|
||||||
|
pub raw: LuneReleaseData,
|
||||||
|
}
|
Loading…
Reference in a new issue