Skip to content

Commit

Permalink
feat: support type filter in list API and CLI
Browse files Browse the repository at this point in the history
Closes #2068.

Signed-off-by: Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
  • Loading branch information
AlekSi authored and talos-bot committed Dec 24, 2020
1 parent 5590fe1 commit f3465b8
Show file tree
Hide file tree
Showing 11 changed files with 1,401 additions and 1,181 deletions.
20 changes: 16 additions & 4 deletions api/machine/machine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -276,22 +276,34 @@ message CopyRequest {
string root_path = 1;
}

// ListRequest describes a request to list the contents of a directory
// ListRequest describes a request to list the contents of a directory.
message ListRequest {
// Root indicates the root directory for the list. If not indicated, '/' is
// Root indicates the root directory for the list. If not indicated, '/' is
// presumed.
string root = 1;
// Recurse indicates that subdirectories should be recursed.
bool recurse = 2;
// RecursionDepth indicates how many levels of subdirectories should be
// recursed. The default (0) indicates that no limit should be enforced.
// recursed. The default (0) indicates that no limit should be enforced.
int32 recursion_depth = 3;
// File type.
enum Type {
// Regular file (not directory, symlink, etc).
REGULAR = 0;
// Directory.
DIRECTORY = 1;
// Symbolic link.
SYMLINK = 2;
}
// Types indicates what file type should be returned. If not indicated,
// all files will be returned.
repeated Type types = 4;
}

// DiskUsageRequest describes a request to list disk usage of directories and regular files
message DiskUsageRequest {
// RecursionDepth indicates how many levels of subdirectories should be
// recursed. The default (0) indicates that no limit should be enforced.
// recursed. The default (0) indicates that no limit should be enforced.
int32 recursion_depth = 1;
// All write sizes for all files, not just directories.
bool all = 2;
Expand Down
29 changes: 29 additions & 0 deletions cmd/talosctl/cmd/talos/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"time"

Expand All @@ -28,6 +29,7 @@ var (
recurse bool
recursionDepth int32
humanizeFlag bool
types []string
)

// lsCmd represents the ls command.
Expand All @@ -45,10 +47,29 @@ var lsCmd = &cobra.Command{
rootDir = args[0]
}

// handle all variants: --type=f,l; -tfl; etc
var reqTypes []machineapi.ListRequest_Type
for _, typ := range types {
for _, t := range typ {
// handle both `find -type X` and os.FileMode.String() designations
switch t {
case 'f':
reqTypes = append(reqTypes, machineapi.ListRequest_REGULAR)
case 'd':
reqTypes = append(reqTypes, machineapi.ListRequest_DIRECTORY)
case 'l', 'L':
reqTypes = append(reqTypes, machineapi.ListRequest_SYMLINK)
default:
return fmt.Errorf("invalid file type: %s", string(t))
}
}
}

stream, err := c.LS(ctx, &machineapi.ListRequest{
Root: rootDir,
Recurse: recurse,
RecursionDepth: recursionDepth,
Types: reqTypes,
})
if err != nil {
return fmt.Errorf("error fetching logs: %s", err)
Expand Down Expand Up @@ -172,9 +193,17 @@ var lsCmd = &cobra.Command{
}

func init() {
typesHelp := strings.Join([]string{
"filter by specified types:",
"f" + "\t" + "regular file",
"d" + "\t" + "directory",
"l, L" + "\t" + "symbolic link",
}, "\n")

lsCmd.Flags().BoolVarP(&long, "long", "l", false, "display additional file details")
lsCmd.Flags().BoolVarP(&recurse, "recurse", "r", false, "recurse into subdirectories")
lsCmd.Flags().BoolVarP(&humanizeFlag, "humanize", "H", false, "humanize size and time in the output")
lsCmd.Flags().Int32VarP(&recursionDepth, "depth", "d", 0, "maximum recursion depth")
lsCmd.Flags().StringSliceVarP(&types, "type", "t", nil, typesHelp)
addCommand(lsCmd)
}
27 changes: 23 additions & 4 deletions internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,8 @@ func (s *Server) Copy(req *machine.CopyRequest, obj machine.MachineService_CopyS
}

// List implements the machine.MachineServer interface.
//
//nolint:gocyclo
func (s *Server) List(req *machine.ListRequest, obj machine.MachineService_ListServer) error {
if req == nil {
req = new(machine.ListRequest)
Expand All @@ -682,17 +684,34 @@ func (s *Server) List(req *machine.ListRequest, obj machine.MachineService_ListS
req.Root = "/"
}

var maxDepth int
var opts []archiver.WalkerOption

if req.Recurse {
if req.RecursionDepth == 0 {
maxDepth = -1
opts = append(opts, archiver.WithMaxRecurseDepth(-1))
} else {
maxDepth = int(req.RecursionDepth)
opts = append(opts, archiver.WithMaxRecurseDepth(int(req.RecursionDepth)))
}
}

files, err := archiver.Walker(obj.Context(), req.Root, archiver.WithMaxRecurseDepth(maxDepth))
if len(req.Types) > 0 {
types := make([]archiver.FileType, 0, len(req.Types))

for _, t := range req.Types {
switch t {
case machine.ListRequest_REGULAR:
types = append(types, archiver.RegularFileType)
case machine.ListRequest_DIRECTORY:
types = append(types, archiver.DirectoryFileType)
case machine.ListRequest_SYMLINK:
types = append(types, archiver.SymlinkFileType)
}
}

opts = append(opts, archiver.WithFileTypes(types...))
}

files, err := archiver.Walker(obj.Context(), req.Root, opts...)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/archiver/archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package archiver provides a service to archive part of the filesystem into tar archive
// Package archiver provides a service to archive part of the filesystem into tar archive.
package archiver

import (
Expand Down
3 changes: 1 addition & 2 deletions pkg/archiver/archiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package archiver provides a service to archive part of the filesystem into tar archive
package archiver_test

import (
Expand Down Expand Up @@ -38,7 +37,7 @@ var filesFixture = []struct {
},
{
Path: "/dev/random",
Mode: 0o600 | os.ModeCharDevice,
Mode: 0o600 | os.ModeDevice | os.ModeCharDevice,
},
{
Path: "/usr/bin/cp",
Expand Down
1 change: 0 additions & 1 deletion pkg/archiver/tar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package archiver provides a service to archive part of the filesystem into tar archive
package archiver_test

import (
Expand Down
51 changes: 49 additions & 2 deletions pkg/archiver/walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@ type FileItem struct {
Error error
}

// FileType is a file type.
type FileType int

// File types.
const (
RegularFileType FileType = iota
DirectoryFileType
SymlinkFileType
)

type walkerOptions struct {
skipRoot bool
maxRecurseDepth int
fnmatchPatterns []string
types map[FileType]struct{}
}

// WalkerOption configures Walker.
Expand Down Expand Up @@ -54,7 +65,19 @@ func WithFnmatchPatterns(patterns ...string) WalkerOption {
}
}

// Walker provides a channel of file info/paths for archival
// WithFileTypes filters results by file types.
//
// Default is not to do any filtering.
func WithFileTypes(fileType ...FileType) WalkerOption {
return func(o *walkerOptions) {
o.types = make(map[FileType]struct{}, len(fileType))
for _, t := range fileType {
o.types[t] = struct{}{}
}
}
}

// Walker provides a channel of file info/paths for archival.
//
//nolint: gocyclo
func Walker(ctx context.Context, rootPath string, options ...WalkerOption) (<-chan FileItem, error) {
Expand Down Expand Up @@ -96,6 +119,30 @@ func Walker(ctx context.Context, rootPath string, options ...WalkerOption) (<-ch
item.RelPath, item.Error = filepath.Rel(rootPath, path)
}

// TODO: refactor all those `if item.Error == nil &&` conditions

if item.Error == nil && len(opts.types) > 0 {
var matches bool

for t := range opts.types {
switch t {
case RegularFileType:
matches = fileInfo.Mode()&os.ModeType == 0
case DirectoryFileType:
matches = fileInfo.Mode()&os.ModeDir != 0
case SymlinkFileType:
matches = fileInfo.Mode()&os.ModeSymlink != 0
}
if matches {
break
}
}

if !matches {
return nil
}
}

if item.Error == nil && path == rootPath && opts.skipRoot && fileInfo.IsDir() {
// skip containing directory
return nil
Expand All @@ -106,7 +153,7 @@ func Walker(ctx context.Context, rootPath string, options ...WalkerOption) (<-ch
}

if item.Error == nil && len(opts.fnmatchPatterns) > 0 {
matches := false
var matches bool

for _, pattern := range opts.fnmatchPatterns {
if matches, _ = filepath.Match(pattern, item.RelPath); matches { //nolint: errcheck
Expand Down
29 changes: 27 additions & 2 deletions pkg/archiver/walker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package archiver provides a service to archive part of the filesystem into tar archive
package archiver_test

import (
Expand Down Expand Up @@ -104,12 +103,21 @@ func (suite *WalkerSuite) TestIterationSymlink() {
err := os.Mkdir(original, 0o755)
suite.Require().NoError(err)

newname := filepath.Join(suite.tmpDir, "new")
defer func() {
err = os.RemoveAll(original)
suite.Require().NoError(err)
}()

// NB: We make this a relative symlink to make the test more complete.
newname := filepath.Join(suite.tmpDir, "new")
err = os.Symlink("original", newname)
suite.Require().NoError(err)

defer func() {
err = os.Remove(newname)
suite.Require().NoError(err)
}()

err = ioutil.WriteFile(filepath.Join(original, "original.txt"), []byte{}, 0o666)
suite.Require().NoError(err)

Expand All @@ -131,6 +139,23 @@ func (suite *WalkerSuite) TestIterationNotFound() {
suite.Require().Error(err)
}

func (suite *WalkerSuite) TestIterationTypes() {
ch, err := archiver.Walker(context.Background(), suite.tmpDir, archiver.WithFileTypes(archiver.DirectoryFileType))
suite.Require().NoError(err)

relPaths := []string(nil)

for fi := range ch {
suite.Require().NoError(fi.Error)
relPaths = append(relPaths, fi.RelPath)
}

suite.Assert().Equal([]string{
".", "dev", "etc", "etc/certs", "lib", "usr", "usr/bin",
},
relPaths)
}

func TestWalkerSuite(t *testing.T) {
suite.Run(t, new(WalkerSuite))
}

0 comments on commit f3465b8

Please sign in to comment.