Skip to content

Feature Request: Support mounting S3 bucket subdirectories/prefixes in mountBucket #334

@tendev-liam

Description

@tendev-liam

Summary

Add support for mounting a specific subdirectory (prefix) within an S3 bucket, rather than only supporting mounting the entire bucket root.

s3fs-fuse Support

s3fs-fuse already supports this via the bucket path syntax:

s3fs mybucket:/path/prefix/ /mountpoint

Reference: https://github.com/s3fs-fuse/s3fs-fuse/wiki/Fuse-Over-Amazon#prefix-default-coming-soon

"For now, you can specify via s3fs mybucket:/path/prefix/"

Proposed API

// Mount entire bucket (current behavior)
await sandbox.mountBucket('my-bucket', '/mnt/data', options);

// Mount specific subdirectory (new behavior)
await sandbox.mountBucket('my-bucket', '/mnt/data', {
  ...options,
  prefix: '/123/data/'
});

Current Workaround

I've implemented this locally via a patch, just putting the prefix in with the bucket name.

diff --git a/dist/index.js b/dist/index.js
index 6cc68fe..0e24855 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -2700,14 +2700,15 @@ var Sandbox = class extends Container {
 	};
 }
 async mountBucket(bucket, mountPath, options) {
-	this.logger.info(`Mounting bucket ${bucket} to ${mountPath}`);
+	const { bucketName, s3fsSource } = this.parseBucketSource(bucket);
+	this.logger.info(`Mounting bucket ${bucketName} to ${mountPath}`);
 	this.validateMountOptions(bucket, mountPath, options);
 	const provider = options.provider || detectProviderFromUrl(options.endpoint);
 	this.logger.debug(`Detected provider: ${provider || "unknown"}`, { explicitProvider: options.provider });
 	const credentials = detectCredentials(options, this.envVars);
 	const passwordFilePath = this.generatePasswordFilePath();
 	this.activeMounts.set(mountPath, {
-		bucket,
+		bucket: s3fsSource,
 		mountPath,
 		endpoint: options.endpoint,
 		provider,
@@ -2715,9 +2716,9 @@ var Sandbox = class extends Container {
 		mounted: false
 	});
 	try {
-		await this.createPasswordFile(passwordFilePath, bucket, credentials);
+		await this.createPasswordFile(passwordFilePath, bucketName, credentials);
 		await this.exec(`mkdir -p ${shellEscape(mountPath)}`);
-		await this.executeS3FSMount(bucket, mountPath, options, provider, passwordFilePath);
+		await this.executeS3FSMount(s3fsSource, mountPath, options, provider, passwordFilePath);
 		this.activeMounts.set(mountPath, {
 			bucket,
 			mountPath,
@@ -2752,6 +2753,14 @@ var Sandbox = class extends Container {
 	}
 	this.logger.info(`Successfully unmounted bucket from ${mountPath}`);
 }
+parseBucketSource(bucket) {
+	const idx = bucket.indexOf(':/');
+	if (idx === -1) {
+		return { bucketName: bucket, s3fsSource: bucket };
+	}
+	return { bucketName: bucket.slice(0, idx), s3fsSource: bucket };
+}
+
 /**
 * Validate mount options
 */
@@ -2762,7 +2771,7 @@ var Sandbox = class extends Container {
 	} catch (error) {
 		throw new InvalidMountConfigError(`Invalid endpoint URL: "${options.endpoint}". Must be a valid HTTP(S) URL.`);
 	}
-	if (!/^[a-z0-9]([a-z0-9.-]{0,61}[a-z0-9])?$/.test(bucket)) throw new InvalidMountConfigError(`Invalid bucket name: "${bucket}". Bucket names must be 3-63 characters, lowercase alphanumeric, dots, or hyphens, and cannot start/end with dots or hyphens.`);
+	if (!/^[a-z0-9]([a-z0-9.-]{0,61}[a-z0-9])?(?::\/.*\/)?$/.test(bucket)) throw new InvalidMountConfigError(`Invalid bucket name: "${bucket}". Bucket names must be 3-63 characters, lowercase alphanumeric, dots, or hyphens, and cannot start/end with dots or hyphens.`);
 	if (!mountPath.startsWith("/")) throw new InvalidMountConfigError(`Mount path must be absolute (start with /): "${mountPath}"`);
 	if (this.activeMounts.has(mountPath)) throw new InvalidMountConfigError(`Mount path "${mountPath}" is already in use by bucket "${this.activeMounts.get(mountPath)?.bucket}". Unmount the existing bucket first or use a different mount path.`);
 }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions