Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/Azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ the container for you - you'll need to do that yourself.

You can also define a prefix that will be prepended to the keys of all cache objects created and read within the container, effectively creating a scope. To do that use the `SCCACHE_AZURE_KEY_PREFIX` environment variable. This can be useful when sharing a bucket with another application.

The `SCCACHE_AZURE_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

**Important:** The environment variables are only taken into account when the server starts, i.e. only on the first run.
2 changes: 2 additions & 0 deletions docs/COS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ You **must** specify the endpoint URL using the `SCCACHE_COS_ENDPOINT` environme

You can also define a prefix that will be prepended to the keys of all cache objects created and read within the COS bucket, effectively creating a scope. To do that use the `SCCACHE_COS_KEY_PREFIX` environment variable. This can be useful when sharing a bucket with another application.

The `SCCACHE_COS_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

## Credentials

Sccache is able to load credentials from environment variables: `TENCENTCLOUD_SECRET_ID` and `TENCENTCLOUD_SECRET_KEY`. More details about the access of COS bucket can be found at the [introduction page](https://www.tencentcloud.com/document/product/436/7751).
8 changes: 8 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export SCCACHE_MULTILEVEL_WRITE_ERROR_POLICY="all"
* `SCCACHE_REGION` s3 region, required if using AWS S3
* `SCCACHE_S3_USE_SSL` s3 endpoint requires TLS, set this to `true`
* `SCCACHE_S3_KEY_PREFIX` s3 key prefix (optional)
* `SCCACHE_S3_RW_MODE` allows to use s3 backend in read-only mode if set to `READ_ONLY`

The endpoint used then becomes `${SCCACHE_BUCKET}.s3-{SCCACHE_REGION}.amazonaws.com`.
If you are not using the default endpoint and `SCCACHE_REGION` is undefined, it
Expand All @@ -254,6 +255,7 @@ will default to `us-east-1`.
* `SCCACHE_REDIS_DB` redis database (optional, default is 0).
* `SCCACHE_REDIS_EXPIRATION` / `SCCACHE_REDIS_TTL` ttl for redis cache, don't set for default behavior.
* `SCCACHE_REDIS_KEY_PREFIX` key prefix (optional).
* `SCCACHE_REDIS_RW_MODE` allows to use redis backend in read-only mode if set to `READ_ONLY`

The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.

Expand All @@ -265,6 +267,7 @@ The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.
* `SCCACHE_MEMCACHED_PASSWORD` memcached password (optional).
* `SCCACHE_MEMCACHED_EXPIRATION` ttl for memcached cache, don't set for default behavior.
* `SCCACHE_MEMCACHED_KEY_PREFIX` key prefix (optional).
* `SCCACHE_MEMCACHED_RW_MODE` allows to use memcached backend in read-only mode if set to `READ_ONLY`

#### gcs

Expand All @@ -278,13 +281,15 @@ The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.
* `SCCACHE_AZURE_CONNECTION_STRING`
* `SCCACHE_AZURE_BLOB_CONTAINER`
* `SCCACHE_AZURE_KEY_PREFIX`
* `SCCACHE_AZURE_RW_MODE`

#### gha

* `SCCACHE_GHA_CACHE_URL` / `ACTIONS_RESULTS_URL` GitHub Actions cache API URL
* `SCCACHE_GHA_RUNTIME_TOKEN` / `ACTIONS_RUNTIME_TOKEN` GitHub Actions access token
* `SCCACHE_GHA_CACHE_TO` cache key to write
* `SCCACHE_GHA_CACHE_FROM` comma separated list of cache keys to read from
* `SCCACHE_GHA_RW_MODE` allows to use GHA cache backend in read-only mode if set to `READ_ONLY`

#### webdav

Expand All @@ -293,6 +298,7 @@ The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.
* `SCCACHE_WEBDAV_USERNAME` a username to authenticate with webdav service (optional).
* `SCCACHE_WEBDAV_PASSWORD` a password to authenticate with webdav service (optional).
* `SCCACHE_WEBDAV_TOKEN` a token to authenticate with webdav service (optional) - may be used instead of login & password.
* `SCCACHE_WEBDAV_RW_MODE` allows to use webdav backend in read-only mode if set to `READ_ONLY`

#### OSS

Expand All @@ -302,6 +308,7 @@ The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.
* `ALIBABA_CLOUD_ACCESS_KEY_ID`
* `ALIBABA_CLOUD_ACCESS_KEY_SECRET`
* `SCCACHE_OSS_NO_CREDENTIALS`
* `SCCACHE_OSS_RW_MODE`

#### Tencent Cloud Object Storage (COS)

Expand All @@ -310,3 +317,4 @@ The full url appears then as `redis://user:passwd@1.2.3.4:6379/?db=1`.
* `SCCACHE_COS_KEY_PREFIX`
* `TENCENTCLOUD_SECRET_ID`
* `TENCENTCLOUD_SECRET_KEY`
* `SCCACHE_COS_RW_MODE`
2 changes: 2 additions & 0 deletions docs/GHA.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This cache type will need tokens like `ACTIONS_RESULTS_URL` and `ACTIONS_RUNTIME
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
```

The `SCCACHE_GHA_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

## Behavior

In case sccache reaches the rate limit of the service, the build will continue, but the storage might not be performed.
2 changes: 2 additions & 0 deletions docs/Memcached.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Set `SCCACHE_MEMCACHED_EXPIRATION` to the default expiration seconds of memcache

Set `SCCACHE_MEMCACHED_KEY_PREFIX` if you want to prefix all cache keys. This can be
useful when sharing a Memcached instance with another application or cache.

The `SCCACHE_MEMCACHED_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.
2 changes: 2 additions & 0 deletions docs/OSS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ You **must** specify the endpoint URL using the `SCCACHE_OSS_ENDPOINT` environme

You can also define a prefix that will be prepended to the keys of all cache objects created and read within the OSS bucket, effectively creating a scope. To do that use the `SCCACHE_OSS_KEY_PREFIX` environment variable. This can be useful when sharing a bucket with another application.

The `SCCACHE_OSS_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

## Credentials

Sccache is able to load credentials from environment variables: `ALIBABA_CLOUD_ACCESS_KEY_ID` and `ALIBABA_CLOUD_ACCESS_KEY_SECRET`.
Expand Down
2 changes: 2 additions & 0 deletions docs/Redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ useful when sharing a Redis instance with another application or cache.
`SCCACHE_REDIS` is deprecated for security reasons, use `SCCACHE_REDIS_ENDPOINT` instead. See mozilla/sccache#2083 for details.
If you really want to use `SCCACHE_REDIS`, you should URL in format `redis://[[<username>]:<passwd>@]<hostname>[:port][/?db=<db>]`.

The `SCCACHE_REDIS_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

## Deprecated API Examples

Use the local Redis instance with no password:
Expand Down
4 changes: 3 additions & 1 deletion docs/S3.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ If you want to use S3 storage for the sccache cache, you need to set the followi

If your endpoint requires HTTPS/TLS, set `SCCACHE_S3_USE_SSL=true`. If you don't need a secure network layer, HTTP (`SCCACHE_S3_USE_SSL=false`) might be better for performance.

Enable server-side encryption with s3 managed key (SSE-S3), set `SCCACHE_S3_SERVER_SIDE_ENCRYPTION=true`.
Enable server-side encryption with s3 managed key (SSE-S3), set `SCCACHE_S3_SERVER_SIDE_ENCRYPTION=true`.
More details about encryption [here](https://opendal.apache.org/docs/services/s3/#server-side-encryption) and documentation [here](https://docs.rs/opendal/latest/opendal/services/struct.S3.html#method.server_side_encryption_with_s3_key).

You can also define a prefix that will be prepended to the keys of all cache objects created and read within the S3 bucket, effectively creating a scope. To do that use the `SCCACHE_S3_KEY_PREFIX` environment variable. This can be useful when sharing a bucket with another application.

The `SCCACHE_S3_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

# R2

Cloudflare R2 is an S3-compatible object storage and works with the same configuration options as above. To use R2, you **must** define `SCCACHE_ENDPOINT`, otherwise sccache will default to AWS as the endpoint to hit. R2 also requires endpoint connections to be secure, therefore `https://` either needs to be included in `SCCACHE_ENDPOINT` or `SCCACHE_S3_USE_SSL=true` can be used, if the protocol is omitted. There are no regions in R2, so `SCCACHE_REGION` must point to `auto`. The below environment variables are recommended.
Expand Down
2 changes: 2 additions & 0 deletions docs/Webdav.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The following services all expose a WebDAV interface and can be used as a backen
Set `SCCACHE_WEBDAV_ENDPOINT` to an appropriate webdav service endpoint to enable remote caching.
Set `SCCACHE_WEBDAV_KEY_PREFIX` to specify the key prefix of cache.

The `SCCACHE_WEBDAV_RW_MODE` environment variable can be set to `READ_ONLY` to make sccache use this backend in read-only mode. The default is `READ_WRITE`.

## Credentials

Sccache is able to load credentials from the following sources:
Expand Down
76 changes: 62 additions & 14 deletions src/cache/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub trait Storage: Send + Sync {
pub struct RemoteStorage {
operator: opendal::Operator,
basedirs: Vec<Vec<u8>>,
rw_mode: CacheMode,
}

#[cfg(any(
Expand All @@ -184,8 +185,12 @@ pub struct RemoteStorage {
feature = "cos"
))]
impl RemoteStorage {
pub fn new(operator: opendal::Operator, basedirs: Vec<Vec<u8>>) -> Self {
Self { operator, basedirs }
pub fn new(operator: opendal::Operator, basedirs: Vec<Vec<u8>>, rw_mode: CacheMode) -> Self {
Self {
operator,
basedirs,
rw_mode,
}
}
}

Expand Down Expand Up @@ -249,6 +254,13 @@ impl Storage for RemoteStorage {
Err(err) => bail!("cache storage failed to read: {:?}", err),
}

// No need to check write if we are in manually-set read-only mode
if self.rw_mode == CacheMode::ReadOnly {
let mode = CacheMode::ReadOnly;
debug!("storage check result: {mode:?} (manually set)");
return Ok(mode);
}

let can_write = match self.operator.write(path, "Hello, World!").await {
Ok(_) => true,
Err(err) if err.kind() == ErrorKind::AlreadyExists => true,
Expand Down Expand Up @@ -337,6 +349,10 @@ impl Storage for RemoteStorage {
trace!("opendal::Operator::put_raw({}, {} bytes)", key, data.len());
let start = std::time::Instant::now();

if self.rw_mode == CacheMode::ReadOnly {
bail!("storage is read-only");
}

self.operator.write(&normalize_key(key), data).await?;

Ok(start.elapsed())
Expand Down Expand Up @@ -367,11 +383,12 @@ pub fn build_single_cache(
connection_string,
container,
key_prefix,
rw_mode,
}) => {
debug!("Init azure cache with container {container}, key_prefix {key_prefix}");
let operator = AzureBlobCache::build(connection_string, container, key_prefix)
.map_err(|err| anyhow!("create azure cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
Ok(Arc::new(storage))
}
#[cfg(feature = "gcs")]
Expand All @@ -394,16 +411,18 @@ pub fn build_single_cache(
credential_url.as_deref(),
)
.map_err(|err| anyhow!("create gcs cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
Ok(Arc::new(storage))
}
#[cfg(feature = "gha")]
CacheType::GHA(config::GHACacheConfig { version, .. }) => {
CacheType::GHA(config::GHACacheConfig {
version, rw_mode, ..
}) => {
debug!("Init gha cache with version {version}");

let operator = GHACache::build(version)
.map_err(|err| anyhow!("create gha cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
Ok(Arc::new(storage))
}
#[cfg(feature = "memcached")]
Expand All @@ -413,6 +432,7 @@ pub fn build_single_cache(
password,
expiration,
key_prefix,
rw_mode,
}) => {
debug!("Init memcached cache with url {url}");

Expand All @@ -424,7 +444,7 @@ pub fn build_single_cache(
*expiration,
)
.map_err(|err| anyhow!("create memcached cache failed: {err:?}"))?;
let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), (*rw_mode).into());
Ok(Arc::new(storage))
}
#[cfg(feature = "redis")]
Expand All @@ -437,6 +457,7 @@ pub fn build_single_cache(
url,
ttl,
key_prefix,
rw_mode,
}) => {
let storage = match (endpoint, cluster_endpoints, url) {
(Some(url), None, None) => {
Expand Down Expand Up @@ -472,7 +493,7 @@ pub fn build_single_cache(
_ => bail!("Only one of `endpoint`, `cluster_endpoints`, `url` must be set"),
}
.map_err(|err| anyhow!("create redis cache failed: {err:?}"))?;
let storage = RemoteStorage::new(storage, basedirs.to_vec());
let storage = RemoteStorage::new(storage, basedirs.to_vec(), (*rw_mode).into());
Ok(Arc::new(storage))
}
#[cfg(feature = "s3")]
Expand All @@ -492,7 +513,7 @@ pub fn build_single_cache(
.build()
.map_err(|err| anyhow!("create s3 cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
Ok(Arc::new(storage))
}
#[cfg(feature = "webdav")]
Expand All @@ -508,7 +529,7 @@ pub fn build_single_cache(
)
.map_err(|err| anyhow!("create webdav cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
Ok(Arc::new(storage))
}
#[cfg(feature = "oss")]
Expand All @@ -526,7 +547,7 @@ pub fn build_single_cache(
)
.map_err(|err| anyhow!("create oss cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
Ok(Arc::new(storage))
}
#[cfg(feature = "cos")]
Expand All @@ -539,7 +560,7 @@ pub fn build_single_cache(
let operator = COSCache::build(&c.bucket, &c.key_prefix, c.endpoint.as_deref())
.map_err(|err| anyhow!("create cos cache failed: {err:?}"))?;

let storage = RemoteStorage::new(operator, basedirs.to_vec());
let storage = RemoteStorage::new(operator, basedirs.to_vec(), c.rw_mode.into());
Ok(Arc::new(storage))
}
#[allow(unreachable_patterns)]
Expand Down Expand Up @@ -680,7 +701,7 @@ mod test {
let basedirs = vec![b"/home/user/project".to_vec(), b"/opt/build".to_vec()];

// Wrap with OperatorStorage
let storage = RemoteStorage::new(operator, basedirs.clone());
let storage = RemoteStorage::new(operator, basedirs.clone(), CacheMode::ReadWrite);

// Verify basedirs are stored and retrieved correctly
assert_eq!(storage.basedirs(), basedirs.as_slice());
Expand All @@ -706,10 +727,37 @@ mod test {
let basedirs = vec![b"/workspace".to_vec()];

// Wrap with OperatorStorage
let storage = RemoteStorage::new(operator, basedirs.clone());
let storage = RemoteStorage::new(operator, basedirs.clone(), CacheMode::ReadWrite);

// Verify basedirs work
assert_eq!(storage.basedirs(), basedirs.as_slice());
assert_eq!(storage.basedirs().len(), 1);
}

#[test]
#[cfg(feature = "redis")]
fn test_operator_storage_redis_with_read_only() {
// Create Redis operator

use crate::test::utils::Waiter;
let operator = crate::cache::redis::RedisCache::build_single(
"redis://localhost:6379",
None,
None,
0,
"test-prefix",
0,
)
.expect("Failed to create Redis cache operator");

// Wrap with OperatorStorage
let storage = RemoteStorage::new(operator, vec![], CacheMode::ReadOnly);

// Verify put fails
let result = storage.put("test", CacheWrite::default()).wait();
match result {
Ok(_) => panic!("expected error, got success {result:?}"),
Err(err) => assert_eq!(err.to_string(), "storage is read-only"),
}
}
}
Loading
Loading