Skip to content
Open
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
114 changes: 114 additions & 0 deletions cmd/ls-count_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package cmd

import "testing"

// TestStorageClassAllowed tests the storage class filtering logic used by the ls command.
func TestStorageClassAllowed(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
storageClass string
filter string
expected bool
}{
{
name: "No filter - allow all",
storageClass: "STANDARD",
filter: "",
expected: true,
},
{
name: "No filter with empty storage class",
storageClass: "",
filter: "",
expected: true,
},
{
name: "Wildcard filter - allow all",
storageClass: "STANDARD",
filter: "*",
expected: true,
},
{
name: "Wildcard filter with empty storage class",
storageClass: "",
filter: "*",
expected: true,
},
{
name: "Empty storage class with specific filter - allow",
storageClass: "",
filter: "GLACIER",
expected: true,
},
{
name: "Matching storage class",
storageClass: "STANDARD",
filter: "STANDARD",
expected: true,
},
{
name: "Matching storage class GLACIER",
storageClass: "GLACIER",
filter: "GLACIER",
expected: true,
},
{
name: "Non-matching storage class",
storageClass: "STANDARD",
filter: "GLACIER",
expected: false,
},
{
name: "Non-matching storage class (different filter)",
storageClass: "GLACIER",
filter: "STANDARD",
expected: false,
},
{
name: "Non-existent storage class filtered",
storageClass: "STANDARD",
filter: "DEEP_ARCHIVE",
expected: false,
},
{
name: "Case-sensitive mismatch",
storageClass: "standard",
filter: "STANDARD",
expected: false,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

content := &ClientContent{StorageClass: tc.storageClass}
got := storageClassAllowed(content, tc.filter)

if got != tc.expected {
t.Fatalf("storageClassAllowed(StorageClass=%q, filter=%q)=%v; want %v",
tc.storageClass, tc.filter, got, tc.expected)
}
})
}
}
13 changes: 11 additions & 2 deletions cmd/ls-main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ var (
Name: "summarize",
Usage: "display summary information (number of objects, total size)",
},
cli.BoolFlag{
Name: "count-only",
Usage: "print only the total count of objects",
},
cli.StringFlag{
Name: "storage-class, sc",
Usage: "filter to specified storage class",
Expand Down Expand Up @@ -109,9 +113,12 @@ EXAMPLES:

9. List all objects on mybucket, summarize the number of objects and total size.
{{.Prompt}} {{.HelpName}} --summarize s3/mybucket/

10. List all objects on mybucket, for the GLACIER storage class
{{.Prompt}} {{.HelpName}} --storage-class 'GLACIER' s3/mybucket
{{.Prompt}} {{.HelpName}} --storage-class 'GLACIER' s3/mybucket

11. Count objects in mybucket without listing them.
{{.Prompt}} {{.HelpName}} --count-only s3/mybucket/
`,
}

Expand Down Expand Up @@ -173,6 +180,7 @@ func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) {
isIncomplete := cliCtx.Bool("incomplete")
withVersions := cliCtx.Bool("versions")
isSummary := cliCtx.Bool("summarize")
countOnly := cliCtx.Bool("count-only")
listZip := cliCtx.Bool("zip")

timeRef := parseRewindFlag(cliCtx.String("rewind"))
Expand All @@ -186,6 +194,7 @@ func checkListSyntax(cliCtx *cli.Context) ([]string, doListOptions) {
isRecursive: isRecursive,
isIncomplete: isIncomplete,
isSummary: isSummary,
countOnly: countOnly,
withVersions: withVersions,
listZip: listZip,
filter: storageClasss,
Expand Down
34 changes: 30 additions & 4 deletions cmd/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,31 @@ type doListOptions struct {
isRecursive bool
isIncomplete bool
isSummary bool
countOnly bool
withVersions bool
listZip bool
filter string
}

// storageClassAllowed checks if a content entry should be included based on storage class filter.
// Returns true if:
// - filter is empty (no filtering)
// - filter is "*" (wildcard, show all)
// - content.StorageClass is empty (always include)
// - content.StorageClass matches filter
func storageClassAllowed(content *ClientContent, filter string) bool {
// No filter or wildcard filter means allow all
if filter == "" || filter == "*" {
return true
}
// If content has no storage class, allow it
if content.StorageClass == "" {
return true
}
// Otherwise, allow only if storage class matches filter
return content.StorageClass == filter
}

// doList - list all entities inside a folder.
func doList(ctx context.Context, clnt Client, o doListOptions) error {
var (
Expand All @@ -241,13 +261,15 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error {
continue
}

if content.StorageClass != "" && o.filter != "" && o.filter != "*" && content.StorageClass != o.filter {
if !storageClassAllowed(content, o.filter) {
continue
}

if lastPath != content.URL.Path {
// Print any object in the current list before reinitializing it
printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)
if !o.countOnly {
printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)
}
lastPath = content.URL.Path
perObjectVersions = []*ClientContent{}
}
Expand All @@ -257,9 +279,13 @@ func doList(ctx context.Context, clnt Client, o doListOptions) error {
totalObjects++
}

printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)
if !o.countOnly {
printObjectVersions(clnt.GetURL(), perObjectVersions, o.withVersions)
}

if o.isSummary {
if o.countOnly {
console.Println(totalObjects)
} else if o.isSummary {
printMsg(summaryMessage{
TotalObjects: totalObjects,
TotalSize: totalSize,
Expand Down