feat(init): Implement 'ls' command (#721)
Fixes #719 Signed-off-by: Seán C McCord <ulexus@gmail.com>
This commit is contained in:
parent
f5969d2c6c
commit
532a53bfaf
107
cmd/osctl/cmd/ls.go
Normal file
107
cmd/osctl/cmd/ls.go
Normal file
@ -0,0 +1,107 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/client"
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/helpers"
|
||||
"github.com/talos-systems/talos/internal/app/osd/proto"
|
||||
)
|
||||
|
||||
// lsCmd represents the ls command
|
||||
var lsCmd = &cobra.Command{
|
||||
Use: "ls [path]",
|
||||
Short: "Retrieve a directory listing",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
setupClient(func(c *client.Client) {
|
||||
rootDir := "/"
|
||||
if len(args) > 0 {
|
||||
rootDir = args[0]
|
||||
}
|
||||
long, err := cmd.Flags().GetBool("long")
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to parse long flag: %v", err)
|
||||
}
|
||||
recurse, err := cmd.Flags().GetBool("recurse")
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to parse recurse flag: %v", err)
|
||||
}
|
||||
recursionDepth, err := cmd.Flags().GetInt32("depth")
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to parse depth flag: %v", err)
|
||||
}
|
||||
|
||||
stream, err := c.LS(globalCtx, proto.LSRequest{
|
||||
Root: rootDir,
|
||||
Recurse: recurse,
|
||||
RecursionDepth: recursionDepth,
|
||||
})
|
||||
if err != nil {
|
||||
helpers.Fatalf("error fetching logs: %s", err)
|
||||
}
|
||||
|
||||
if !long {
|
||||
for {
|
||||
info, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF || status.Code(err) == codes.Canceled {
|
||||
return
|
||||
}
|
||||
helpers.Fatalf("error streaming logs: %s", err)
|
||||
}
|
||||
if info.Error != "" {
|
||||
fmt.Fprintf(os.Stderr, "error reading file %s: %s\n", info.Name, info.Error)
|
||||
} else {
|
||||
fmt.Println(info.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||
fmt.Fprintln(w, "MODE\tSIZE(B)\tLASTMOD\tNAME")
|
||||
for {
|
||||
info, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF || status.Code(err) == codes.Canceled {
|
||||
helpers.Should(w.Flush())
|
||||
return
|
||||
}
|
||||
helpers.Fatalf("error streaming logs: %s", err)
|
||||
}
|
||||
|
||||
if info.Error != "" {
|
||||
fmt.Fprintf(os.Stderr, "error reading file %s: %s\n", info.Name, info.Error)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t%d\t%s\t%s\n",
|
||||
os.FileMode(info.Mode).String(),
|
||||
info.Size,
|
||||
time.Unix(info.Modified, 0).Format("Jan 2 2006"),
|
||||
info.Name,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
lsCmd.Flags().StringVarP(&target, "target", "t", "", "target the specificed node")
|
||||
lsCmd.Flags().BoolP("long", "l", false, "display additional file details")
|
||||
lsCmd.Flags().BoolP("recurse", "r", false, "recurse into subdirectories")
|
||||
lsCmd.Flags().Int32P("depth", "d", 0, "maximum recursion depth")
|
||||
rootCmd.AddCommand(lsCmd)
|
||||
}
|
@ -220,6 +220,11 @@ func (c *Client) DF(ctx context.Context) (*proto.DFReply, error) {
|
||||
return c.client.DF(ctx, &empty.Empty{})
|
||||
}
|
||||
|
||||
// LS implements the proto.OSDClient interface.
|
||||
func (c *Client) LS(ctx context.Context, req proto.LSRequest) (stream proto.OSD_LSClient, err error) {
|
||||
return c.client.LS(ctx, &req)
|
||||
}
|
||||
|
||||
// Upgrade initiates a Talos upgrade ... and implements the proto.OSDClient
|
||||
// interface
|
||||
func (c *Client) Upgrade(ctx context.Context, asseturl string) (string, error) {
|
||||
|
@ -39,6 +39,9 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// OSPathSeparator is the string version of the os.PathSeparator
|
||||
const OSPathSeparator = string(os.PathSeparator)
|
||||
|
||||
// Registrator is the concrete type that implements the factory.Registrator and
|
||||
// proto.OSDServer interfaces.
|
||||
type Registrator struct {
|
||||
@ -449,6 +452,57 @@ func (r *Registrator) DF(ctx context.Context, in *empty.Empty) (reply *proto.DFR
|
||||
return reply, multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// LS implements the proto.OSDServer interface.
|
||||
func (r *Registrator) LS(req *proto.LSRequest, s proto.OSD_LSServer) error {
|
||||
|
||||
if req == nil {
|
||||
req = new(proto.LSRequest)
|
||||
}
|
||||
if !strings.HasPrefix(req.Root, OSPathSeparator) {
|
||||
// Make sure we use complete paths
|
||||
req.Root = OSPathSeparator + req.Root
|
||||
}
|
||||
req.Root = strings.TrimSuffix(req.Root, OSPathSeparator)
|
||||
if req.Root == "" {
|
||||
req.Root = "/"
|
||||
}
|
||||
|
||||
var maxDepth int
|
||||
if req.Recurse {
|
||||
if req.RecursionDepth == 0 {
|
||||
maxDepth = -1
|
||||
} else {
|
||||
maxDepth = int(req.RecursionDepth)
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Walk(req.Root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// Send errors upstream so that we do not abort the path walk
|
||||
return s.Send(&proto.FileInfo{
|
||||
Name: path,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
err = s.Send(&proto.FileInfo{
|
||||
Name: path,
|
||||
Size: info.Size(),
|
||||
Mode: uint32(info.Mode()),
|
||||
Modified: info.ModTime().Unix(),
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() && atMaxDepth(maxDepth, req.Root, path) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func k8slogs(ctx context.Context, req *proto.LogsRequest) (string, error) {
|
||||
inspector, err := containers.NewInspector(ctx, req.Namespace)
|
||||
if err != nil {
|
||||
@ -482,3 +536,14 @@ func k8slogs(ctx context.Context, req *proto.LogsRequest) (string, error) {
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func atMaxDepth(max int, root, cur string) bool {
|
||||
if max < 0 {
|
||||
return false
|
||||
}
|
||||
if root == cur {
|
||||
// always recurse the root directory
|
||||
return false
|
||||
}
|
||||
return (strings.Count(cur, OSPathSeparator) - strings.Count(root, OSPathSeparator)) >= max
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ service OSD {
|
||||
rpc Dmesg(google.protobuf.Empty) returns (Data) {}
|
||||
rpc Kubeconfig(google.protobuf.Empty) returns (Data) {}
|
||||
rpc Logs(LogsRequest) returns (stream Data) {}
|
||||
rpc LS(LSRequest) returns (stream FileInfo) {}
|
||||
rpc Processes(ProcessesRequest) returns (ProcessesReply) {}
|
||||
rpc Reset(google.protobuf.Empty) returns (ResetReply) {}
|
||||
rpc Restart(RestartRequest) returns (RestartReply) {}
|
||||
@ -99,3 +100,38 @@ message DFStat {
|
||||
uint64 available = 3;
|
||||
string mounted_on = 4;
|
||||
}
|
||||
|
||||
// LSRequest describes a request to list the contents of a directory
|
||||
message LSRequest {
|
||||
|
||||
// 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.
|
||||
int32 recursion_depth = 3;
|
||||
}
|
||||
|
||||
// FileInfo describes a file or directory's information
|
||||
message FileInfo {
|
||||
|
||||
// Name is the name (including prefixed path) of the file or directory
|
||||
string name = 1;
|
||||
|
||||
// Size indicates the number of bytes contained within the file
|
||||
int64 size = 2;
|
||||
|
||||
// Mode is the bitmap of UNIX mode/permission flags of the file
|
||||
uint32 mode = 3;
|
||||
|
||||
// Modified indicates the UNIX timestamp at which the file was last modified
|
||||
int64 modified = 4; // TODO: unix timestamp or include proto's Date type
|
||||
|
||||
// IsDir indicates that the file is a directory
|
||||
bool is_dir = 5;
|
||||
|
||||
// Error describes any error encountered while trying to read the file information.
|
||||
string error = 6;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user