diff --git a/pkg/mountpoint/args.go b/pkg/mountpoint/args.go index 9673b6b08..e26fb2be2 100644 --- a/pkg/mountpoint/args.go +++ b/pkg/mountpoint/args.go @@ -65,23 +65,25 @@ func ParseArgs(passedArgs []string) Args { for _, a := range passedArgs { var key, value string - - parts := strings.SplitN(strings.Trim(a, " "), "=", 2) - if len(parts) == 2 { - // Ex: `--key=value` or `key=value` - key, value = parts[0], parts[1] + trimmed := strings.Trim(a, " ") + + // Find positions of first space and first equals + spacePos := strings.Index(trimmed, " ") + equalsPos := strings.Index(trimmed, "=") + + // Determine which separator to use based on which comes first + if spacePos != -1 && (equalsPos == -1 || spacePos < equalsPos) { + // Space comes first or equals not found - use space separator + parts := strings.SplitN(trimmed, " ", 2) + key, value = parts[0], strings.Trim(parts[1], " ") + } else if equalsPos != -1 { + // Equals comes first or space not found - use equals separator + parts := strings.SplitN(trimmed, "=", 2) + key, value = parts[0], strings.Trim(parts[1], " ") } else { - // Ex: `--key value` or `key value` - // Ex: `--key` or `key` - parts = strings.SplitN(strings.Trim(parts[0], " "), " ", 2) - if len(parts) == 1 { - // Ex: `--key` or `key` - key = parts[0] - value = ArgNoValue - } else { - // Ex: `--key value` or `key value` - key, value = parts[0], strings.Trim(parts[1], " ") - } + // No separators found - just a key + key = trimmed + value = ArgNoValue } // prepend -- if it's not already there diff --git a/pkg/mountpoint/args_test.go b/pkg/mountpoint/args_test.go index 07862113f..72846f111 100644 --- a/pkg/mountpoint/args_test.go +++ b/pkg/mountpoint/args_test.go @@ -152,6 +152,58 @@ func TestParsingMountpointArgs(t *testing.T) { "--read-only", }, }, + { + name: "prefix with space separator and equals in value", + input: []string{ + "allow-delete", + "region us-west-2", + "prefix kwk3-di/sub=vault/", + }, + want: []string{ + "--allow-delete", + "--prefix=kwk3-di/sub=vault/", + "--region=us-west-2", + }, + }, + { + name: "prefix with equals separator and space in value", + input: []string{ + "allow-delete", + "region us-west-2", + "prefix=kwk3-di/sub vault/", + }, + want: []string{ + "--allow-delete", + "--prefix=kwk3-di/sub vault/", + "--region=us-west-2", + }, + }, + { + name: "prefix with equals in key and value", + input: []string{ + "allow-delete", + "region us-west-2", + "prefix=kwk3-di/sub=vault/", + }, + want: []string{ + "--allow-delete", + "--prefix=kwk3-di/sub=vault/", + "--region=us-west-2", + }, + }, + { + name: "prefix with multiple equals in value", + input: []string{ + "allow-delete", + "region us-west-2", + "prefix env=prod/app=web/version=1.0/", + }, + want: []string{ + "--allow-delete", + "--prefix=env=prod/app=web/version=1.0/", + "--region=us-west-2", + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { diff --git a/tests/e2e/customsuites/mountoptions.go b/tests/e2e/customsuites/mountoptions.go index c30f3bcd9..1e86e640a 100644 --- a/tests/e2e/customsuites/mountoptions.go +++ b/tests/e2e/customsuites/mountoptions.go @@ -664,4 +664,85 @@ func (t *s3CSIMountOptionsTestSuite) DefineTests(driver storageframework.TestDri framework.Logf("Successfully verified bidirectional visibility between S3 API and mounted volume with prefix") }) + + // This test verifies that the --prefix mount option correctly handles + // prefix values that contain equals signs (=). This is a regression test + // for a bug where the argument parser would split on the first "=" in the + // mount option value, producing an incorrect --prefix argument. + // + // For example, "prefix=env=prod/" was incorrectly parsed as --prefix=env + // (truncating "=prod/") instead of --prefix=env=prod/. + // + // Test scenario: + // + // [Pod] + // | + // ↓ + // [S3 Volume with --prefix=key=val/] + // | + // ↓ + // [Files stored under key=val/ in S3] + // + // Expected results: + // - Files created in the mounted volume are stored under the full prefix (key=val/) in S3 + // - The S3 object keys correctly include the equals sign in the prefix + ginkgo.It("should handle prefix values containing equals signs", func(ctx context.Context) { + s3Client := s3client.New("", "", "") + + // Use a prefix that contains an equals sign — this is the edge case + // that the parser fix addresses. + prefix := "env=prod/" + resource := BuildVolumeWithOptions( + ctx, + l.config, + pattern, + DefaultNonRootUser, + DefaultNonRootGroup, + "", + fmt.Sprintf("prefix=%s", prefix), + ) + l.resources = append(l.resources, resource) + + bucketName := GetBucketNameFromVolumeResource(resource) + if bucketName == "" { + framework.Failf("failed to extract bucket name from volume resource") + } + + ginkgo.By("Creating pod with a volume using prefix containing equals sign") + pod := MakeNonRootPodWithVolume(f.Namespace.Name, []*v1.PersistentVolumeClaim{resource.Pvc}, "") + var err error + pod, err = createPod(ctx, f.ClientSet, f.Namespace.Name, pod) + framework.ExpectNoError(err) + defer func() { + framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)) + }() + + volPath := "/mnt/volume1" + testFileName := "equals-prefix-test.txt" + fileInVol := fmt.Sprintf("%s/%s", volPath, testFileName) + testContent := "Testing prefix with equals sign" + + ginkgo.By("Writing a file to the volume") + WriteAndVerifyFile(f, pod, fileInVol, testContent) + + ginkgo.By("Verifying file can be read back from the pod") + e2evolume.VerifyExecInPodSucceed(f, pod, fmt.Sprintf("cat %s | grep -q '%s'", fileInVol, testContent)) + + ginkgo.By(fmt.Sprintf("Verifying object exists under prefix %q in S3 bucket", prefix)) + err = s3Client.VerifyObjectsExistInS3(ctx, bucketName, prefix, []string{testFileName}) + framework.ExpectNoError(err, "failed to verify object exists under prefix %q — "+ + "the prefix parser may have incorrectly split on the equals sign in the value", prefix) + + ginkgo.By("Verifying all objects in the bucket are under the expected prefix") + rootListOutput, err := s3Client.ListObjects(ctx, bucketName) + framework.ExpectNoError(err, "failed to list objects in bucket") + + for _, obj := range rootListOutput.Contents { + if !strings.HasPrefix(*obj.Key, prefix) { + framework.Failf("Found unexpected object %q outside prefix %q — "+ + "this suggests the prefix was truncated at the equals sign", *obj.Key, prefix) + } + } + framework.Logf("Successfully verified prefix with equals sign: all objects under %q", prefix) + }) }