@@ -310,6 +310,131 @@ func TestIsLocal(t *testing.T) {
310310 }
311311}
312312
313+ func TestS3ProfileOption (t * testing.T ) {
314+ // Test parsing profile from URL query parameter
315+ testProfile := "my-test-profile"
316+ s , err := ParseBackend (
317+ fmt .Sprintf ("s3://bucket/prefix/?profile=%s" , url .QueryEscape (testProfile )),
318+ nil ,
319+ )
320+ require .NoError (t , err )
321+ s3 := s .GetS3 ()
322+ require .NotNil (t , s3 )
323+ require .Equal (t , "bucket" , s3 .Bucket )
324+ require .Equal (t , "prefix" , s3 .Prefix )
325+ require .Equal (t , testProfile , s3 .Profile )
326+
327+ // Test with BackendOptions
328+ s3opt := & BackendOptions {
329+ S3 : S3BackendOptions {
330+ Profile : "profile-from-options" ,
331+ },
332+ }
333+ s , err = ParseBackend ("s3://bucket2/prefix/" , s3opt )
334+ require .NoError (t , err )
335+ s3 = s .GetS3 ()
336+ require .NotNil (t , s3 )
337+ require .Equal (t , "bucket2" , s3 .Bucket )
338+ require .Equal (t , "prefix" , s3 .Prefix )
339+ require .Equal (t , "profile-from-options" , s3 .Profile )
340+
341+ // Test profile with other S3 options
342+ s , err = ParseBackend (
343+ "s3://bucket3/prefix/?profile=dev-profile®ion=us-west-2&endpoint=https://s3.example.com" ,
344+ nil ,
345+ )
346+ require .NoError (t , err )
347+ s3 = s .GetS3 ()
348+ require .NotNil (t , s3 )
349+ require .Equal (t , "bucket3" , s3 .Bucket )
350+ require .Equal (t , "prefix" , s3 .Prefix )
351+ require .Equal (t , "dev-profile" , s3 .Profile )
352+ require .Equal (t , "us-west-2" , s3 .Region )
353+ require .Equal (t , "https://s3.example.com" , s3 .Endpoint )
354+
355+ // Test empty profile (should remain empty)
356+ s , err = ParseBackend ("s3://bucket4/prefix/" , nil )
357+ require .NoError (t , err )
358+ s3 = s .GetS3 ()
359+ require .NotNil (t , s3 )
360+ require .Equal (t , "bucket4" , s3 .Bucket )
361+ require .Equal (t , "prefix" , s3 .Prefix )
362+ require .Equal (t , "" , s3 .Profile ) // Should be empty when not specified
363+ }
364+
365+ func TestS3ProfileCredentialsValidation (t * testing.T ) {
366+ // Test that profile makes credentials optional in URL parsing
367+
368+ // Case 1: Profile without credentials - should be allowed
369+ s , err := ParseBackend ("s3://bucket/prefix/?profile=production®ion=us-west-2" , nil )
370+ require .NoError (t , err , "Should not require credentials when using profile" )
371+ s3 := s .GetS3 ()
372+ require .NotNil (t , s3 )
373+ require .Equal (t , "production" , s3 .Profile )
374+ require .Equal (t , "us-west-2" , s3 .Region )
375+ require .Equal (t , "" , s3 .AccessKey )
376+ require .Equal (t , "" , s3 .SecretAccessKey )
377+
378+ // Case 2: Profile with partial credentials - should be allowed
379+ s , err = ParseBackend ("s3://bucket/prefix/?profile=dev&access-key=override-key" , nil )
380+ require .NoError (t , err , "Should allow partial credentials with profile" )
381+ s3 = s .GetS3 ()
382+ require .NotNil (t , s3 )
383+ require .Equal (t , "dev" , s3 .Profile )
384+ require .Equal (t , "override-key" , s3 .AccessKey )
385+ require .Equal (t , "" , s3 .SecretAccessKey ) // No secret key, but should be OK with profile
386+
387+ // Case 3: Profile with explicit credentials override
388+ s , err = ParseBackend ("s3://bucket/prefix/?profile=staging&access-key=explicit-access&secret-access-key=explicit-secret" , nil )
389+ require .NoError (t , err )
390+ s3 = s .GetS3 ()
391+ require .NotNil (t , s3 )
392+ require .Equal (t , "staging" , s3 .Profile )
393+ require .Equal (t , "explicit-access" , s3 .AccessKey )
394+ require .Equal (t , "explicit-secret" , s3 .SecretAccessKey )
395+ }
396+
397+ func TestS3NoProfileCredentialsValidation (t * testing.T ) {
398+ // Test that without profile, credential validation still applies
399+
400+ // Case 1: No profile, partial credentials - should fail
401+ s3opt := & BackendOptions {
402+ S3 : S3BackendOptions {
403+ AccessKey : "only-access-key" ,
404+ // Missing SecretAccessKey
405+ },
406+ }
407+ _ , err := ParseBackend ("s3://bucket/prefix/" , s3opt )
408+ require .Error (t , err , "Should fail when access key provided without secret key" )
409+ require .Contains (t , err .Error (), "secret_access_key not found" )
410+
411+ // Case 2: No profile, both credentials - should be valid
412+ s3opt2 := & BackendOptions {
413+ S3 : S3BackendOptions {
414+ AccessKey : "test-access" ,
415+ SecretAccessKey : "test-secret" ,
416+ },
417+ }
418+ s , err := ParseBackend ("s3://bucket/prefix/" , s3opt2 )
419+ require .NoError (t , err )
420+ s3Backend2 := s .GetS3 ()
421+ require .Equal (t , "test-access" , s3Backend2 .AccessKey )
422+ require .Equal (t , "test-secret" , s3Backend2 .SecretAccessKey )
423+
424+ // Case 3: No profile, no credentials - should be valid (IAM role, etc.)
425+ s3opt3 := & BackendOptions {
426+ S3 : S3BackendOptions {
427+ Region : "us-east-1" ,
428+ // No credentials
429+ },
430+ }
431+ s , err = ParseBackend ("s3://bucket/prefix/" , s3opt3 )
432+ require .NoError (t , err , "Should allow no credentials when no profile (for IAM roles, etc.)" )
433+ s3Backend3 := s .GetS3 ()
434+ require .Equal (t , "" , s3Backend3 .AccessKey )
435+ require .Equal (t , "" , s3Backend3 .SecretAccessKey )
436+ }
437+
313438func TestParseBackend (t * testing.T ) {
314439 {
315440 backendOptions := & BackendOptions {
0 commit comments