Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions pkg/mountpoint/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions pkg/mountpoint/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
81 changes: 81 additions & 0 deletions tests/e2e/customsuites/mountoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Loading