Initial stabilization #1

Merged
dzu merged 22 commits from stabilize-v1 into main 2026-02-21 04:54:42 +01:00
3 changed files with 32 additions and 173 deletions
Showing only changes of commit d110f903b1 - Show all commits

move out the common code
All checks were successful
Build and update the action / build-and-upload (push) Successful in 2m45s

Artem Diachenko 2026-02-19 10:17:59 +00:00

23
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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();
} }