Skip to content

Commit 602a557

Browse files
authored
Merge pull request #2838 from itowlson/fix-windows-asset-subdirectories-in-registries
Fix issues with publishing or consuming registries on Windows
2 parents 485b040 + cd6f8af commit 602a557

File tree

1 file changed

+45
-6
lines changed

1 file changed

+45
-6
lines changed

crates/oci/src/client.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,18 @@ impl Client {
360360
}
361361
// Can unwrap because we got to 'entry' from walking 'source'
362362
let rel_path = entry.path().strip_prefix(source).unwrap();
363+
// Paths must be in portable (forward slash) format in the registry,
364+
// so that they can be placed correctly on any host system
365+
let rel_path = portable_path(rel_path);
366+
363367
tracing::trace!("Adding new layer for asset {rel_path:?}");
364368
// Construct and push layer, adding its digest to the locked component files Vec
365369
let layer = Self::data_layer(entry.path(), DATA_MEDIATYPE.to_string()).await?;
366370
let content = self.content_ref_for_layer(&layer);
367371
let content_inline = content.inline.is_some();
368372
files.push(ContentPath {
369373
content,
370-
path: rel_path.into(),
374+
path: rel_path,
371375
});
372376
// As a workaround for OCI implementations that don't support very small blobs,
373377
// don't push very small content that has been inlined into the manifest:
@@ -461,14 +465,14 @@ impl Client {
461465
let p = self
462466
.cache
463467
.manifests_dir()
464-
.join(reference.registry())
468+
.join(fs_safe_segment(reference.registry()))
465469
.join(reference.repository())
466470
.join(reference.tag().unwrap_or(LATEST_TAG));
467471

468472
if !p.is_dir() {
469-
fs::create_dir_all(&p)
470-
.await
471-
.context("cannot find directory for OCI manifest")?;
473+
fs::create_dir_all(&p).await.with_context(|| {
474+
format!("cannot create directory {} for OCI manifest", p.display())
475+
})?;
472476
}
473477

474478
Ok(p.join(MANIFEST_FILE))
@@ -483,7 +487,7 @@ impl Client {
483487
let p = self
484488
.cache
485489
.manifests_dir()
486-
.join(reference.registry())
490+
.join(fs_safe_segment(reference.registry()))
487491
.join(reference.repository())
488492
.join(reference.tag().unwrap_or(LATEST_TAG));
489493

@@ -782,6 +786,41 @@ fn add_inferred(map: &mut BTreeMap<String, String>, key: &str, value: Option<Str
782786
}
783787
}
784788

789+
/// Takes a relative path and turns it into a format that is safe
790+
/// for putting into a registry where it might end up on any host.
791+
#[cfg(target_os = "windows")]
792+
fn portable_path(rel_path: &Path) -> PathBuf {
793+
assert!(
794+
rel_path.is_relative(),
795+
"portable_path requires paths to be relative"
796+
);
797+
let portable_path = rel_path.to_string_lossy().replace('\\', "/");
798+
PathBuf::from(portable_path)
799+
}
800+
801+
/// Takes a relative path and turns it into a format that is safe
802+
/// for putting into a registry where it might end up on any host.
803+
/// This is a no-op on Unix systems, but is needed for Windows.
804+
#[cfg(not(target_os = "windows"))]
805+
fn portable_path(rel_path: &Path) -> PathBuf {
806+
rel_path.into()
807+
}
808+
809+
/// Takes a string intended for use as part of a path and makes it
810+
/// compatible with the local filesystem.
811+
#[cfg(target_os = "windows")]
812+
fn fs_safe_segment(segment: &str) -> impl AsRef<Path> {
813+
segment.replace(':', "_")
814+
}
815+
816+
/// Takes a string intended for use as part of a path and makes it
817+
/// compatible with the local filesystem.
818+
/// This is a no-op on Unix systems, but is needed for Windows.
819+
#[cfg(not(target_os = "windows"))]
820+
fn fs_safe_segment(segment: &str) -> impl AsRef<Path> + '_ {
821+
segment
822+
}
823+
785824
#[cfg(test)]
786825
mod test {
787826
use super::*;

0 commit comments

Comments
 (0)