Initial stabilization #1
3 changed files with 32 additions and 173 deletions
commit
d110f903b1
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -29,6 +29,18 @@ dependencies = [
|
||||||
"yaml_serde",
|
"yaml_serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anode-cache-common"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.dzuchun.ing/actions/cache-common-src.git#9de30e2308eedaf79384260c8c915573cad011b4"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"isahc",
|
||||||
|
"secrecy",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anode-macros"
|
name = "anode-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -98,11 +110,10 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anode",
|
"anode",
|
||||||
"anode-build",
|
"anode-build",
|
||||||
|
"anode-cache-common",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"flate2",
|
"flate2",
|
||||||
"isahc",
|
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"serde_json",
|
|
||||||
"tar",
|
"tar",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
@ -817,9 +828,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.115"
|
version = "2.0.116"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
|
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -922,9 +933,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.23"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anode = { git = "https://git.dzuchun.ing/dzu/anode.git", version = "0.1.0" }
|
anode = { git = "https://git.dzuchun.ing/dzu/anode.git", version = "0.1.0" }
|
||||||
|
anode-cache-common = { git = "https://git.dzuchun.ing/actions/cache-common-src.git", version = "0.1.0" }
|
||||||
anyhow = "1.0.101"
|
anyhow = "1.0.101"
|
||||||
flate2 = "1.1.9"
|
flate2 = "1.1.9"
|
||||||
isahc = { version = "1.7.2", features = ["static-ssl", "static-curl"] }
|
|
||||||
secrecy = "0.10.3"
|
secrecy = "0.10.3"
|
||||||
serde_json = "1.0.149"
|
|
||||||
tar = "0.4.44"
|
tar = "0.4.44"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
|
|
||||||
|
|
|
||||||
179
src/main.rs
179
src/main.rs
|
|
@ -1,55 +1,11 @@
|
||||||
use std::{ffi::OsString, io::Read, path::PathBuf, str::FromStr, string::FromUtf8Error};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anode_cache_common::{CacheKey, Client, actions_token, key_parser, paths_parser};
|
||||||
|
use anyhow::{Context, Result};
|
||||||
use flate2::{Compression, GzBuilder};
|
use flate2::{Compression, GzBuilder};
|
||||||
use isahc::{
|
use secrecy::ExposeSecret;
|
||||||
HttpClient, HttpClientBuilder, Request,
|
|
||||||
auth::{Authentication, Credentials},
|
|
||||||
config::Configurable,
|
|
||||||
http::{Method, StatusCode, header},
|
|
||||||
};
|
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
|
||||||
use tar::{Builder, HeaderMode};
|
use tar::{Builder, HeaderMode};
|
||||||
|
|
||||||
fn paths_parser(input: OsString) -> Result<Vec<PathBuf>, FromUtf8Error> {
|
|
||||||
let input = String::from_utf8(input.into_encoded_bytes())?;
|
|
||||||
let mut paths = Vec::new();
|
|
||||||
for path in input.split('\n').filter(|s| !s.is_empty()) {
|
|
||||||
paths.push(match PathBuf::from_str(path) {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CacheKey(String);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
enum CacheKeyError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Utf8(#[from] FromUtf8Error),
|
|
||||||
#[error("Cache key is at most 512 characters long. {len} characters long key provided")]
|
|
||||||
Length { len: usize },
|
|
||||||
#[error("Cache key cannot contain commas. Comma found at offset {offset}")]
|
|
||||||
Comma { offset: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_parser(input: OsString) -> Result<CacheKey, CacheKeyError> {
|
|
||||||
let input = String::from_utf8(input.into_encoded_bytes())?;
|
|
||||||
let mut len = 0;
|
|
||||||
for (offset, ch) in input.char_indices() {
|
|
||||||
if ch == ',' {
|
|
||||||
return Err(CacheKeyError::Comma { offset });
|
|
||||||
}
|
|
||||||
len += 1;
|
|
||||||
}
|
|
||||||
if len > 512 {
|
|
||||||
return Err(CacheKeyError::Length { len });
|
|
||||||
}
|
|
||||||
Ok(CacheKey(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, anode::Input)]
|
#[derive(Debug, anode::Input)]
|
||||||
#[name = "Cache save"]
|
#[name = "Cache save"]
|
||||||
struct Input {
|
struct Input {
|
||||||
|
|
@ -67,123 +23,16 @@ struct Input {
|
||||||
key: CacheKey,
|
key: CacheKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
fn main() -> Result<()> {
|
||||||
enum ActionsTokenError {
|
|
||||||
#[error("Could not find action runtime token")]
|
|
||||||
NotPresent,
|
|
||||||
#[error("Action runtime token was not UTF-8 -encoded")]
|
|
||||||
NotUtf8,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn actions_token() -> Result<SecretString, ActionsTokenError> {
|
|
||||||
let os_str = std::env::var_os("ACTIONS_RUNTIME_TOKEN").ok_or(ActionsTokenError::NotPresent)?;
|
|
||||||
let str =
|
|
||||||
String::from_utf8(os_str.into_encoded_bytes()).map_err(|_| ActionsTokenError::NotUtf8)?;
|
|
||||||
Ok(SecretString::new(str.into_boxed_str()))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Client {
|
|
||||||
http_client: HttpClient,
|
|
||||||
base_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
struct CacheId(u64);
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
fn reserve(&self, key: &CacheKey, cache_size: usize) -> Result<CacheId> {
|
|
||||||
let uri = format!("{}_apis/artifactcache/caches", self.base_url);
|
|
||||||
let request = Request::builder()
|
|
||||||
.uri(uri)
|
|
||||||
.method(Method::POST)
|
|
||||||
.body(
|
|
||||||
serde_json::json! {{
|
|
||||||
"key": &key.0,
|
|
||||||
"version": "0",
|
|
||||||
"cacheSize": cache_size,
|
|
||||||
}}
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.context("building the request")?;
|
|
||||||
let mut response = self
|
|
||||||
.http_client
|
|
||||||
.send(request)
|
|
||||||
.context("sending the request")?;
|
|
||||||
let status = response.status();
|
|
||||||
let mut body = Vec::with_capacity(100);
|
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.context("reading response body")?;
|
|
||||||
if status != StatusCode::OK {
|
|
||||||
bail!(
|
|
||||||
"Failed reserving a cache (status {status}): {:?}",
|
|
||||||
String::from_utf8_lossy(&body)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let body_value =
|
|
||||||
serde_json::from_slice::<serde_json::Value>(&body).context("parsing response body")?;
|
|
||||||
|
|
||||||
println!("{body_value:#?}");
|
|
||||||
|
|
||||||
let cache_id = body_value
|
|
||||||
.get("cacheId")
|
|
||||||
.context("response.cacheId")?
|
|
||||||
.as_u64()
|
|
||||||
.context("cacheId as u64")?;
|
|
||||||
|
|
||||||
Ok(CacheId(cache_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save(&self, id: CacheId, archive: Vec<u8>) -> Result<()> {
|
|
||||||
let uri = format!("{}_apis/artifactcache/caches/{}", self.base_url, id.0);
|
|
||||||
let request = Request::builder()
|
|
||||||
.uri(uri)
|
|
||||||
.method(Method::PATCH)
|
|
||||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
|
||||||
.header(
|
|
||||||
header::CONTENT_RANGE,
|
|
||||||
format!("bytes {}-{}/*", 0, archive.len()),
|
|
||||||
)
|
|
||||||
.body(archive)
|
|
||||||
.context("building the request")?;
|
|
||||||
let mut response = self
|
|
||||||
.http_client
|
|
||||||
.send(request)
|
|
||||||
.context("sending the request")?;
|
|
||||||
let status = response.status();
|
|
||||||
let mut body = Vec::with_capacity(100);
|
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.context("reading response body")?;
|
|
||||||
if status != StatusCode::OK {
|
|
||||||
bail!(
|
|
||||||
"Failed saving a cache (status {status}): {:?}",
|
|
||||||
String::from_utf8_lossy(&body)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// parse inputs
|
// parse inputs
|
||||||
let Input { path, key } = Input::get();
|
let Input { path, key } = Input::get();
|
||||||
let actions_token = actions_token().unwrap();
|
let actions_token = actions_token().context("reading actions token")?;
|
||||||
|
|
||||||
|
let cache_url = std::env::var("ACTIONS_CACHE_URL").context("reading action cache url")?;
|
||||||
|
|
||||||
// set up http client
|
// set up http client
|
||||||
let http_client = HttpClientBuilder::new()
|
let client = Client::new(actions_token.expose_secret(), cache_url)
|
||||||
.authentication(Authentication::basic())
|
.context("intializing cache client")?;
|
||||||
.credentials(Credentials::new("Bearer", actions_token.expose_secret()))
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let client = Client {
|
|
||||||
http_client,
|
|
||||||
base_url: std::env::var("ACTIONS_CACHE_URL").unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// collect and compress the cached files
|
// collect and compress the cached files
|
||||||
let mut output = Vec::<u8>::with_capacity(1024 * 1024); // assume large cache size
|
let mut output = Vec::<u8>::with_capacity(1024 * 1024); // assume large cache size
|
||||||
|
|
@ -204,9 +53,9 @@ fn main() {
|
||||||
archive.finish().unwrap();
|
archive.finish().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache_id = client.reserve(&key, output.len()).unwrap();
|
client
|
||||||
|
.save_cache(&key, output)
|
||||||
|
.context("saving the cache")?;
|
||||||
|
|
||||||
println!("Reserved cache with id {cache_id:?}");
|
Ok(())
|
||||||
|
|
||||||
client.save(cache_id, output).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue