diff --git a/docs/Configuration.md b/docs/Configuration.md index bac3b52fd..fa53aa9af 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -153,6 +153,8 @@ username = "alice" password = "secret12" # Mutually exclusive with username & password. Bearer token value token = "token123" +# Skip PROPFIND/MKCOL calls before writes, for servers that don't support them +disable_create_dir = false [cache.oss] bucket = "name" @@ -325,6 +327,7 @@ When using Entra ID, credentials are read from the ambient `AZURE_*` environment * `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` +* `SCCACHE_WEBDAV_DISABLE_CREATE_DIR` skip PROPFIND/MKCOL calls before writes, for servers that don't support them (optional). #### OSS diff --git a/docs/Webdav.md b/docs/Webdav.md index ccfca5931..bb00349a6 100644 --- a/docs/Webdav.md +++ b/docs/Webdav.md @@ -18,3 +18,12 @@ Sccache is able to load credentials from the following sources: - Set `SCCACHE_WEBDAV_USERNAME`/`SCCACHE_WEBDAV_PASSWORD` to specify the username/password pair for basic authentication. - Set `SCCACHE_WEBDAV_TOKEN` to specify the token value for bearer token authentication. + +## Disable Create Dir + +Some WebDAV servers don't support the `PROPFIND`/`MKCOL` methods that opendal's WebDAV +backend otherwise issues before every write to ensure the parent directory exists. Sonatype +Nexus raw repositories are one example: they only implement `GET`/`HEAD`/`PUT`/`DELETE` and +reject `MKCOL` with `405 Method Not Allowed`, which makes every cache write fail. + +Set `SCCACHE_WEBDAV_DISABLE_CREATE_DIR=true` to skip these calls during writes. diff --git a/src/cache/cache.rs b/src/cache/cache.rs index c11dddfc1..5590d1e52 100644 --- a/src/cache/cache.rs +++ b/src/cache/cache.rs @@ -604,6 +604,7 @@ pub fn build_single_cache( c.username.as_deref(), c.password.as_deref(), c.token.as_deref(), + c.disable_create_dir, ) .map_err(|err| anyhow!("create webdav cache failed: {err:?}"))?; diff --git a/src/cache/webdav.rs b/src/cache/webdav.rs index acf22cb0c..c4beaad2a 100644 --- a/src/cache/webdav.rs +++ b/src/cache/webdav.rs @@ -29,13 +29,15 @@ impl WebdavCache { username: Option<&str>, password: Option<&str>, token: Option<&str>, + disable_create_dir: bool, ) -> Result { let builder = Webdav::default() .endpoint(endpoint) .root(key_prefix) .username(username.unwrap_or_default()) .password(password.unwrap_or_default()) - .token(token.unwrap_or_default()); + .token(token.unwrap_or_default()) + .disable_create_dir(disable_create_dir); let op = Operator::new(builder)? .with_context(OperationContext::new().with_http_transport(set_user_agent())) diff --git a/src/config.rs b/src/config.rs index 00f3e8c92..56776bbb3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -460,6 +460,10 @@ pub struct WebdavCacheConfig { pub token: Option, #[serde(default)] pub rw_mode: CacheModeConfig, + /// Skip PROPFIND/MKCOL calls before writes, for servers (e.g. Nexus raw + /// repositories) that only implement GET/HEAD/PUT/DELETE. + #[serde(default)] + pub disable_create_dir: bool, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1136,6 +1140,8 @@ fn config_from_env() -> Result { let token = string_from_env_var("SCCACHE_WEBDAV_TOKEN"); let rw_mode = cache_mode_from_env_var("SCCACHE_WEBDAV_RW_MODE").unwrap_or(CacheModeConfig::ReadWrite); + let disable_create_dir = + bool_from_env_var("SCCACHE_WEBDAV_DISABLE_CREATE_DIR")?.unwrap_or(false); Some(WebdavCacheConfig { endpoint, @@ -1144,6 +1150,7 @@ fn config_from_env() -> Result { password, token, rw_mode, + disable_create_dir, }) } else { None @@ -2490,6 +2497,59 @@ fn test_s3_sse_kms_from_env() { } } +#[test] +#[serial(config_from_env)] +#[cfg(feature = "webdav")] +fn test_webdav_disable_create_dir_from_env() { + unsafe { + env::set_var("SCCACHE_WEBDAV_ENDPOINT", "https://example.com/webdav"); + env::set_var("SCCACHE_WEBDAV_DISABLE_CREATE_DIR", "true"); + } + + let cfg = config_from_env(); + + unsafe { + env::remove_var("SCCACHE_WEBDAV_ENDPOINT"); + env::remove_var("SCCACHE_WEBDAV_DISABLE_CREATE_DIR"); + } + + let env_cfg = cfg.unwrap(); + match env_cfg.cache.webdav { + Some(WebdavCacheConfig { + disable_create_dir, .. + }) => { + assert!(disable_create_dir); + } + None => unreachable!(), + } +} + +#[test] +#[serial(config_from_env)] +#[cfg(feature = "webdav")] +fn test_webdav_disable_create_dir_defaults_to_false() { + unsafe { + env::set_var("SCCACHE_WEBDAV_ENDPOINT", "https://example.com/webdav"); + env::remove_var("SCCACHE_WEBDAV_DISABLE_CREATE_DIR"); + } + + let cfg = config_from_env(); + + unsafe { + env::remove_var("SCCACHE_WEBDAV_ENDPOINT"); + } + + let env_cfg = cfg.unwrap(); + match env_cfg.cache.webdav { + Some(WebdavCacheConfig { + disable_create_dir, .. + }) => { + assert!(!disable_create_dir); + } + None => unreachable!(), + } +} + #[test] #[serial(config_from_env)] #[cfg(feature = "azure")] @@ -2848,6 +2908,7 @@ key_prefix = "webdavprefix" username = "webdavusername" password = "webdavpassword" token = "webdavtoken" +disable_create_dir = true [cache.oss] bucket = "name" @@ -2934,6 +2995,7 @@ key_prefix = "cosprefix" password: Some("webdavpassword".to_string()), token: Some("webdavtoken".to_string()), rw_mode: CacheModeConfig::ReadWrite, + disable_create_dir: true, }), oss: Some(OSSCacheConfig { bucket: "name".to_owned(),