Skip to content

Commit e868ad5

Browse files
committed
feat(tests/nixos/s3-binary-cache-store): exercise access with both s3:// and https:// URLs
When accessing public S3 buckets, users can use either: 1. Direct HTTPS URLs (recommended) - bypasses credential lookup entirely 2. s3:// URLs - works due to 403->404 special-casing in `HttpBinaryCacheStore` This commit: - Adds `parametrize_url_schemes` decorator for testing both URL types cleanly - Adds tests to validate both approaches work for public buckets - Updates documentation recommending HTTPS URLs for public access
1 parent 09d6847 commit e868ad5

File tree

2 files changed

+174
-11
lines changed

2 files changed

+174
-11
lines changed

src/libstore/s3-binary-cache-store.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,28 @@ For S3 compatible binary caches, consult that cache's documentation.
1010

1111
### Anonymous reads to your S3-compatible binary cache
1212

13-
> If your binary cache is publicly accessible and does not require authentication,
14-
> it is simplest to use the [HTTP Binary Cache Store] rather than S3 Binary Cache Store with
15-
> <https://example-nix-cache.s3.amazonaws.com> instead of <s3://example-nix-cache>.
13+
For publicly accessible binary caches that don't require authentication, you have two options:
14+
15+
**Option 1: HTTP URLs (Recommended)**
16+
17+
Use direct HTTPS URLs to avoid S3 credential lookup:
18+
19+
```
20+
# AWS S3 (virtual-hosted style)
21+
https://bucket-name.s3.region.amazonaws.com
22+
23+
# AWS S3 (path style)
24+
https://s3.region.amazonaws.com/bucket-name
25+
26+
# S3-compatible services
27+
https://endpoint/bucket-name
28+
```
29+
30+
This approach bypasses AWS credential lookup entirely, avoiding timeouts on non-AWS infrastructure.
31+
32+
**Option 2: S3 URLs**
33+
34+
You can still use `s3://bucket-name` URLs, though this may be slower due to credential lookup attempts before falling back to unauthenticated access.
1635

1736
Your bucket will need a
1837
[bucket policy](https://docs.aws.amazon.com/AmazonS3/v1/userguide/bucket-policies.html)

tests/nixos/s3-binary-cache-store.nix

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ in
6969
{ nodes }:
7070
# python
7171
''
72+
import functools
7273
import json
7374
import random
7475
import re
@@ -159,7 +160,8 @@ in
159160
versioned: If True, enable versioning on the bucket before populating
160161
"""
161162
def decorator(test_func):
162-
def wrapper():
163+
@functools.wraps(test_func)
164+
def wrapper(*args, **kwargs):
163165
bucket = str(uuid.uuid4())
164166
server.succeed(f"mc mb minio/{bucket}")
165167
try:
@@ -171,7 +173,7 @@ in
171173
store_url = make_s3_url(bucket)
172174
for pkg in populate_bucket:
173175
server.succeed(f"{ENV_WITH_CREDS} nix copy --to '{store_url}' {pkg}")
174-
test_func(bucket)
176+
test_func(bucket, *args, **kwargs)
175177
finally:
176178
server.succeed(f"mc rb --force minio/{bucket}")
177179
# Clean up client store - only delete if path exists
@@ -180,6 +182,38 @@ in
180182
return wrapper
181183
return decorator
182184
185+
def parametrize_url_schemes(test_func):
186+
"""Decorator that runs a test with both s3:// and https:// URL schemes
187+
188+
The decorated test receives a 'url_maker' callable that generates
189+
the appropriate store URL for a given bucket. Each URL scheme gets
190+
a fresh bucket (when combined with setup_s3).
191+
192+
Usage:
193+
@parametrize_url_schemes
194+
@setup_s3(populate_bucket=[PKGS['A']], public=True)
195+
def test_something(bucket, url_maker):
196+
store_url = url_maker(bucket)
197+
# test code uses store_url
198+
"""
199+
@functools.wraps(test_func)
200+
def wrapper():
201+
url_schemes = [
202+
("s3://", lambda b: make_s3_url(b)),
203+
("https://", lambda b: f"http://server:9000/{b}")
204+
]
205+
206+
for scheme_name, url_maker in url_schemes:
207+
print(f"\n → Testing with {scheme_name} URLs")
208+
try:
209+
test_func(url_maker)
210+
except Exception as e:
211+
print(f" ✗ Failed with {scheme_name} URLs")
212+
raise Exception(f"Test failed for {scheme_name} URLs: {e}") from e
213+
print(f" ✓ All checks passed with {scheme_name} URLs")
214+
215+
return wrapper
216+
183217
# ============================================================================
184218
# Test Functions
185219
# ============================================================================
@@ -359,16 +393,17 @@ in
359393
print(" ✓ nix copy works")
360394
print(" ✓ Credentials cached on client")
361395
396+
@parametrize_url_schemes
362397
@setup_s3(populate_bucket=[PKGS['A'], PKGS['B']], public=True)
363-
def test_public_bucket_operations(bucket):
364-
"""Test store operations on public bucket without credentials"""
398+
def test_public_bucket_operations(bucket, url_maker):
399+
"""Test store operations on public bucket using both s3:// and https:// URLs"""
365400
print("\n=== Testing Public Bucket Operations ===")
366401
367-
store_url = make_s3_url(bucket)
402+
store_url = url_maker(bucket)
368403
369404
# Verify store info works without credentials
370405
client.succeed(f"nix store info --store '{store_url}' >&2")
371-
print(" ✓ nix store info works without credentials")
406+
print(" ✓ nix store info works")
372407
373408
# Get and validate store info JSON
374409
info_json = client.succeed(f"nix store info --json --store '{store_url}'")
@@ -383,15 +418,123 @@ in
383418
verify_packages_in_store(client, [PKGS['A'], PKGS['B']], should_exist=False)
384419
385420
# Test copy from public bucket without credentials
386-
client.succeed(
421+
output = client.succeed(
387422
f"nix copy --debug --no-check-sigs "
388423
f"--from '{store_url}' {PKGS['A']} {PKGS['B']} 2>&1"
389424
)
390425
426+
# For HTTPS URLs, verify no credential provider was created
427+
# For s3:// URLs, credential provider might be created but works due to fallback
428+
if store_url.startswith("http://") or store_url.startswith("https://"):
429+
if "creating new AWS credential provider" in output:
430+
print("Debug output:")
431+
print(output)
432+
raise Exception("HTTPS URLs should NOT create AWS credential provider")
433+
print(" ✓ No credential provider created (HTTPS URL)")
434+
else:
435+
print(" ✓ S3 URL works with public bucket")
436+
391437
# Verify packages were copied successfully
392438
verify_packages_in_store(client, [PKGS['A'], PKGS['B']])
439+
print(" ✓ Packages successfully copied from public bucket")
440+
441+
@parametrize_url_schemes
442+
@setup_s3(public=True)
443+
def test_fetchurl_public_bucket(bucket, url_maker):
444+
"""Test fetchurl with public S3 URLs using both s3:// and https:// schemes"""
445+
print("\n=== Testing fetchurl with Public S3 URLs ===")
446+
447+
client.wait_for_unit("network-addresses-eth1.service")
448+
449+
# Upload a test file to the public bucket
450+
test_content = "Public S3 test file content\n"
451+
server.succeed(f"echo -n '{test_content}' > /tmp/public-test-file.txt")
452+
453+
# Calculate expected hash
454+
file_hash = server.succeed(
455+
"nix hash file --type sha256 --base32 /tmp/public-test-file.txt"
456+
).strip()
457+
458+
server.succeed(f"mc cp /tmp/public-test-file.txt minio/{bucket}/public-test.txt")
459+
print(" ✓ Uploaded test file to public bucket")
460+
461+
# Build file URL based on bucket URL
462+
bucket_url = url_maker(bucket)
463+
if bucket_url.startswith("http://") or bucket_url.startswith("https://"):
464+
file_url = f"{bucket_url}/public-test.txt"
465+
else:
466+
# s3:// URL
467+
file_url = make_s3_url(bucket, path="/public-test.txt")
468+
469+
# Test 1: builtins.fetchurl
470+
# ============================
471+
output = client.succeed(
472+
f"nix eval --debug --impure --expr "
473+
f"'builtins.fetchurl {{ name = \"public-test\"; url = \"{file_url}\"; }}' 2>&1"
474+
)
475+
476+
# For HTTPS URLs, verify no AWS credential handling
477+
if file_url.startswith("http://") or file_url.startswith("https://"):
478+
if "creating new AWS credential provider" in output:
479+
print("Debug output:")
480+
print(output)
481+
raise Exception("HTTPS URLs should not trigger AWS credential providers")
482+
483+
if "Pre-resolving AWS credentials" in output:
484+
print("Debug output:")
485+
print(output)
486+
raise Exception("HTTPS URLs should not trigger credential pre-resolution")
487+
488+
print(" ✓ No AWS credential handling for HTTPS URL")
489+
else:
490+
print(" ✓ S3 URL works with public bucket")
491+
492+
print(" ✓ builtins.fetchurl successful")
493+
494+
# Test 2: import <nix/fetchurl.nix> (fixed-output derivation)
495+
# ===========================================================
496+
print("\n Testing import <nix/fetchurl.nix>...")
497+
498+
test_id = random.randint(0, 10000)
499+
test_url = f"{file_url}?test_id={test_id}"
500+
501+
fetchurl_expr = """
502+
import <nix/fetchurl.nix> {{
503+
name = "public-fork-test-{id}";
504+
url = "{url}";
505+
sha256 = "{hash}";
506+
}}
507+
""".format(id=test_id, url=test_url, hash=file_hash)
508+
509+
build_output = client.succeed(
510+
f"nix build --debug --impure --no-link --expr '{fetchurl_expr}' 2>&1"
511+
)
512+
513+
# Verify fork behavior
514+
if "builtin:fetchurl creating fresh FileTransfer instance" not in build_output:
515+
print("Debug output:")
516+
print(build_output)
517+
raise Exception("Expected FileTransfer creation in forked process")
518+
519+
print(" ✓ Forked process creates fresh FileTransfer")
520+
521+
# For HTTPS URLs, verify no AWS credential handling in fork
522+
if file_url.startswith("http://") or file_url.startswith("https://"):
523+
if "creating new AWS credential provider" in build_output:
524+
print("Debug output:")
525+
print(build_output)
526+
raise Exception("HTTPS URLs should not create AWS credential providers in fork")
527+
528+
if "Pre-resolving AWS credentials" in build_output or "Using pre-resolved AWS credentials" in build_output:
529+
print("Debug output:")
530+
print(build_output)
531+
raise Exception("HTTPS URLs should not trigger credential pre-resolution")
532+
533+
print(" ✓ No AWS credential handling in forked process")
534+
else:
535+
print(" ✓ S3 URL works in forked process")
393536
394-
print(" ✓ nix copy from public bucket works without credentials")
537+
print(" ✓ import <nix/fetchurl.nix> successful")
395538
396539
@setup_s3(populate_bucket=[PKGS['A']])
397540
def test_url_format_variations(bucket):
@@ -787,6 +930,7 @@ in
787930
test_fork_credential_preresolution()
788931
test_store_operations()
789932
test_public_bucket_operations()
933+
test_fetchurl_public_bucket()
790934
test_url_format_variations()
791935
test_concurrent_fetches()
792936
test_compression_narinfo_gzip()

0 commit comments

Comments
 (0)