Skip to content

Commit

Permalink
Implement github actions cache
Browse files Browse the repository at this point in the history
  • Loading branch information
huonw committed Sep 26, 2023
1 parent 02c457e commit f30486f
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/python/pants/option/global_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,21 @@ def renderer(_: object) -> str:
"""
),
),
_RemoteAddressScheme(
schemes=("github-actions-cache+http", "github-actions-cache+https"),
supports_execution=False,
experimental=True,
description=softwrap(
f"""
Use the GitHub Actions Cache for fine-grained caching. This requires extracting
`ACTIONS_CACHE_URL` (passing it in `[GLOBAL].remote_store_address`) and
`ACTIONS_RUNTIME_TOKEN` (storing it in a file and passing
`[GLOBAL].remote_oauth_bearer_token_path` or setting `[GLOBAL].remote_store_headers` to
include `authorization: Bearer {{token...}}`). See
{doc_url('remote-caching#github-actions-cache')} for more details.
"""
),
),
)


Expand Down
8 changes: 8 additions & 0 deletions src/rust/engine/fs/store/src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ async fn choose_provider(options: RemoteOptions) -> Result<Arc<dyn ByteStoreProv
"byte-store".to_owned(),
options,
)?))
} else if let Some(url) = address.strip_prefix("github-actions-cache+") {
// TODO: this is relying on python validating that it was set as
// `github-actions-cache+https://...`
Ok(Arc::new(base_opendal::Provider::github_actions_cache(
url,
"byte-store".to_owned(),
options,
)?))
} else {
Err(format!(
"Cannot initialise remote byte store provider with address {address}, as the scheme is not supported",
Expand Down
41 changes: 41 additions & 0 deletions src/rust/engine/fs/store/src/remote/base_opendal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ use async_trait::async_trait;
use bytes::Bytes;
use futures::future;
use hashing::{async_verified_copy, Digest, Fingerprint, EMPTY_DIGEST};
use http::header::AUTHORIZATION;
use opendal::layers::{ConcurrentLimitLayer, RetryLayer, TimeoutLayer};
use opendal::{Builder, Operator};
use tokio::fs::File;
use workunit_store::ObservationMetric;

use super::{ByteStoreProvider, LoadDestination, RemoteOptions};

const GITHUB_ACTIONS_CACHE_VERSION: &str = "pants-1";

#[derive(Debug, Clone, Copy)]
pub enum LoadMode {
Validate,
Expand Down Expand Up @@ -71,6 +74,44 @@ impl Provider {
Provider::new(builder, scope, options)
}

pub fn github_actions_cache(
url: &str,
scope: String,
options: RemoteOptions,
) -> Result<Provider, String> {
let mut builder = opendal::services::Ghac::default();

builder.version(GITHUB_ACTIONS_CACHE_VERSION);
builder.endpoint(url);

// extract the token from the `authorization: Bearer ...` header because OpenDAL's Ghac service
// reasons about it separately (although does just stick it in its own `authorization: Bearer
// ...` header internally).
let header_help_blurb = "Using GitHub Actions Cache remote cache requires a token set in a `authorization: Bearer ...` header, set via [GLOBAL].remote_store_headers or [GLOBAL].remote_oauth_bearer_token_path";
let Some(auth_header_value) = options.headers.get(AUTHORIZATION.as_str()) else {
let existing_headers = options.headers.keys().collect::<Vec<_>>();
return Err(format!(
"Expected to find '{}' header, but only found: {:?}. {}",
AUTHORIZATION, existing_headers, header_help_blurb,
));
};

let Some(token) = auth_header_value.strip_prefix("Bearer ") else {
return Err(format!(
"Expected '{}' header to start with `Bearer `, found value starting with {:?}. {}",
AUTHORIZATION,
// only show the first few characters to not accidentally leak (all of) a secret, but
// still give the user something to start debugging
&auth_header_value[..4],
header_help_blurb,
));
};

builder.runtime_token(token);

Provider::new(builder, scope, options)
}

fn path(&self, fingerprint: Fingerprint) -> String {
// We include the first two bytes as parent directories to make listings less wide.
format!(
Expand Down
8 changes: 8 additions & 0 deletions src/rust/engine/process_execution/remote/src/remote_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ async fn choose_provider(
"action-cache".to_owned(),
remote_options,
)?))
} else if let Some(url) = address.strip_prefix("github-actions-cache+") {
// TODO: this is relying on python validating that it was set as
// `github-actions-cache+https://...`
Ok(Arc::new(base_opendal::Provider::github_actions_cache(
url,
"action-cache".to_owned(),
remote_options,
)?))
} else {
Err(format!(
"Cannot initialise remote action cache provider with address {address}, as the scheme is not supported",
Expand Down

0 comments on commit f30486f

Please sign in to comment.