diff --git a/command/fs.go b/command/fs.go index f42b60334dc1..af252bcc366f 100644 --- a/command/fs.go +++ b/command/fs.go @@ -2,11 +2,14 @@ package command import ( "fmt" + "io" "math/rand" + "os" + "strings" "time" + humanize "github.com/dustin/go-humanize" "github.com/hashicorp/nomad/api" - "github.com/mitchellh/cli" ) type FSCommand struct { @@ -14,7 +17,31 @@ type FSCommand struct { } func (f *FSCommand) Help() string { - return "This command is accessed by using one of the subcommands below." + helpText := ` +Usage: nomad fs + + fs displays either the contents of an allocation directory for the passed allocation, + or displays the file at the given path. The path is relative to the root of the alloc + dir and defaults to root if unspecified. + +General Options: + + ` + generalOptionsUsage() + ` + + -H + Machine friendly output. + + -verbose + Show full information. + + -job + Use a random allocation from a specified job-id. + + -stat + Show file stat information instead of displaying the file, or listing the directory. + +` + return strings.TrimSpace(helpText) } func (f *FSCommand) Synopsis() string { @@ -22,7 +49,173 @@ func (f *FSCommand) Synopsis() string { } func (f *FSCommand) Run(args []string) int { - return cli.RunResultHelp + var verbose, machine, job, stat bool + flags := f.Meta.FlagSet("fs-list", FlagSetClient) + flags.Usage = func() { f.Ui.Output(f.Help()) } + flags.BoolVar(&verbose, "verbose", false, "") + flags.BoolVar(&machine, "H", false, "") + flags.BoolVar(&job, "job", false, "") + flags.BoolVar(&stat, "stat", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + args = flags.Args() + + if len(args) < 1 { + f.Ui.Error("allocation id or -job is required") + return 1 + } + + path := "/" + if len(args) == 2 { + path = args[1] + } + + client, err := f.Meta.Client() + if err != nil { + f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) + return 1 + } + + // If -job is specified, use random allocation, otherwise use provided allocation + allocID := args[0] + if job { + allocID, err = getRandomJobAlloc(client, args[0]) + if err != nil { + f.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err)) + return 1 + } + } + + // Truncate the id unless full length is requested + length := shortId + if verbose { + length = fullId + } + // Query the allocation info + if len(allocID) == 1 { + f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) + return 1 + } + if len(allocID)%2 == 1 { + // Identifiers must be of even length, so we strip off the last byte + // to provide a consistent user experience. + allocID = allocID[:len(allocID)-1] + } + + allocs, _, err := client.Allocations().PrefixList(allocID) + if err != nil { + f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) + return 1 + } + if len(allocs) == 0 { + f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) + return 1 + } + if len(allocs) > 1 { + // Format the allocs + out := make([]string, len(allocs)+1) + out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" + for i, alloc := range allocs { + out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", + limit(alloc.ID, length), + limit(alloc.EvalID, length), + alloc.JobID, + alloc.TaskGroup, + alloc.DesiredStatus, + alloc.ClientStatus, + ) + } + f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out))) + return 0 + } + // Prefix lookup matched a single allocation + alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + if err != nil { + f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) + return 1 + } + + if alloc.DesiredStatus == "failed" { + allocID := limit(alloc.ID, length) + msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run: +nomad alloc-status %s`, allocID, allocID) + f.Ui.Error(msg) + return 0 + } + + // Get file stat info + file, _, err := client.AllocFS().Stat(alloc, path, nil) + if err != nil { + f.Ui.Error(err.Error()) + return 1 + } + + // If we want file stats, print those and exit. + if stat { + // Display the file information + out := make([]string, 2) + out[0] = "Mode|Size|Modified Time|Name" + if file != nil { + fn := file.Name + if file.IsDir { + fn = fmt.Sprintf("%s/", fn) + } + var size string + if machine { + size = fmt.Sprintf("%d", file.Size) + } else { + size = humanize.Bytes(uint64(file.Size)) + } + out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size, + formatTime(file.ModTime), fn) + } + f.Ui.Output(formatList(out)) + return 0 + } + + // Determine if the path is a file or a directory. + if file.IsDir { + // We have a directory, list it. + files, _, err := client.AllocFS().List(alloc, path, nil) + if err != nil { + f.Ui.Error(fmt.Sprintf("Error listing alloc dir: %s", err)) + return 1 + } + // Display the file information in a tabular format + out := make([]string, len(files)+1) + out[0] = "Mode|Size|Modfied Time|Name" + for i, file := range files { + fn := file.Name + if file.IsDir { + fn = fmt.Sprintf("%s/", fn) + } + var size string + if machine { + size = fmt.Sprintf("%d", file.Size) + } else { + size = humanize.Bytes(uint64(file.Size)) + } + out[i+1] = fmt.Sprintf("%s|%s|%s|%s", + file.FileMode, + size, + formatTime(file.ModTime), + fn, + ) + } + f.Ui.Output(formatList(out)) + } else { + // We have a file, cat it. + r, _, err := client.AllocFS().Cat(alloc, path, nil) + if err != nil { + f.Ui.Error(fmt.Sprintf("Error reading file: %s", err)) + return 1 + } + io.Copy(os.Stdout, r) + } + + return 0 } // Get Random Allocation ID from a known jobID. Prefer to use a running allocation, diff --git a/command/fs_cat.go b/command/fs_cat.go deleted file mode 100644 index 801472a67515..000000000000 --- a/command/fs_cat.go +++ /dev/null @@ -1,143 +0,0 @@ -package command - -import ( - "fmt" - "io" - "os" - "strings" -) - -type FSCatCommand struct { - Meta -} - -func (f *FSCatCommand) Help() string { - helpText := ` - Usage: nomad fs cat - - Dispays a file in an allocation directory at the given path. - The path is relative to the allocation directory and defaults to root if unspecified. - - General Options: - - ` + generalOptionsUsage() + ` - -Cat Options: - - -verbose - Show full information. - - -job - Use a random allocation from a specified job-id. -` - return strings.TrimSpace(helpText) -} - -func (f *FSCatCommand) Synopsis() string { - return "Cat a file in an allocation directory" -} - -func (f *FSCatCommand) Run(args []string) int { - var verbose, job bool - flags := f.Meta.FlagSet("fs-list", FlagSetClient) - flags.Usage = func() { f.Ui.Output(f.Help()) } - flags.BoolVar(&verbose, "verbose", false, "") - flags.BoolVar(&job, "job", false, "") - - if err := flags.Parse(args); err != nil { - return 1 - } - args = flags.Args() - - if len(args) < 1 { - f.Ui.Error("allocation id is a required parameter") - return 1 - } - - path := "/" - if len(args) == 2 { - path = args[1] - } - - client, err := f.Meta.Client() - if err != nil { - f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) - return 1 - } - - // If -job is specified, use random allocation, otherwise use provided allocation - allocID := args[0] - if job { - allocID, err = getRandomJobAlloc(client, args[0]) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying API: %v", err)) - return 1 - } - } - - // Truncate the id unless full length is requested - length := shortId - if verbose { - length = fullId - } - // Query the allocation info - if len(allocID) == 1 { - f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) - return 1 - } - if len(allocID)%2 == 1 { - // Identifiers must be of even length, so we strip off the last byte - // to provide a consistent user experience. - allocID = allocID[:len(allocID)-1] - } - - allocs, _, err := client.Allocations().PrefixList(allocID) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) - return 1 - } - if len(allocs) == 0 { - f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) - return 1 - } - if len(allocs) > 1 { - // Format the allocs - out := make([]string, len(allocs)+1) - out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" - for i, alloc := range allocs { - out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", - limit(alloc.ID, length), - limit(alloc.EvalID, length), - alloc.JobID, - alloc.TaskGroup, - alloc.DesiredStatus, - alloc.ClientStatus, - ) - } - f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out))) - return 0 - } - // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) - return 1 - } - - if alloc.DesiredStatus == "failed" { - allocID := limit(alloc.ID, length) - msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run: -nomad alloc-status %s`, allocID, allocID) - f.Ui.Error(msg) - return 0 - } - - // Get the contents of the file - r, _, err := client.AllocFS().Cat(alloc, path, nil) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error reading file: %v", err)) - return 1 - } - io.Copy(os.Stdout, r) - return 0 -} diff --git a/command/fs_ls.go b/command/fs_ls.go deleted file mode 100644 index d86de1bf19e8..000000000000 --- a/command/fs_ls.go +++ /dev/null @@ -1,172 +0,0 @@ -package command - -import ( - "fmt" - "strings" - - humanize "github.com/dustin/go-humanize" -) - -type FSListCommand struct { - Meta -} - -func (f *FSListCommand) Help() string { - helpText := ` -Usage: nomad fs ls - - ls displays the contents of the allocation directory for the passed allocation. The path - is relative to the root of the alloc dir and defaults to root if unspecified. - - General Options: - - ` + generalOptionsUsage() + ` - -Ls Options: - - -H - Machine friendly output. - - -verbose - Show full information. - - -job - Use a random allocation from a specified job-id. - -` - return strings.TrimSpace(helpText) -} - -func (f *FSListCommand) Synopsis() string { - return "List files in an allocation directory" -} - -func (f *FSListCommand) Run(args []string) int { - var verbose bool - var machine bool - var job bool - flags := f.Meta.FlagSet("fs-list", FlagSetClient) - flags.Usage = func() { f.Ui.Output(f.Help()) } - flags.BoolVar(&verbose, "verbose", false, "") - flags.BoolVar(&machine, "H", false, "") - flags.BoolVar(&job, "job", false, "") - - if err := flags.Parse(args); err != nil { - return 1 - } - args = flags.Args() - - if len(args) < 1 { - f.Ui.Error("allocation id is a required parameter") - return 1 - } - - path := "/" - if len(args) == 2 { - path = args[1] - } - - client, err := f.Meta.Client() - if err != nil { - f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) - return 1 - } - - // If -job is specified, use random allocation, otherwise use provided allocation - allocID := args[0] - if job { - allocID, err = getRandomJobAlloc(client, args[0]) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err)) - return 1 - } - } - - // Truncate the id unless full length is requested - length := shortId - if verbose { - length = fullId - } - // Query the allocation info - if len(allocID) == 1 { - f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) - return 1 - } - if len(allocID)%2 == 1 { - // Identifiers must be of even length, so we strip off the last byte - // to provide a consistent user experience. - allocID = allocID[:len(allocID)-1] - } - - allocs, _, err := client.Allocations().PrefixList(allocID) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) - return 1 - } - if len(allocs) == 0 { - f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) - return 1 - } - if len(allocs) > 1 { - // Format the allocs - out := make([]string, len(allocs)+1) - out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" - for i, alloc := range allocs { - out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", - limit(alloc.ID, length), - limit(alloc.EvalID, length), - alloc.JobID, - alloc.TaskGroup, - alloc.DesiredStatus, - alloc.ClientStatus, - ) - } - f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out))) - return 0 - } - // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) - return 1 - } - - if alloc.DesiredStatus == "failed" { - allocID := limit(alloc.ID, length) - msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run: -nomad alloc-status %s`, allocID, allocID) - f.Ui.Error(msg) - return 0 - } - // Get the file at the given path - files, _, err := client.AllocFS().List(alloc, path, nil) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error listing alloc dir: %v", err)) - return 1 - } - - // Display the file information in a tabular format - out := make([]string, len(files)+1) - out[0] = "Mode|Size|Modfied Time|Name" - for i, file := range files { - fn := file.Name - if file.IsDir { - fn = fmt.Sprintf("%s/", fn) - } - var size string - if machine { - size = fmt.Sprintf("%d", file.Size) - } else { - size = humanize.Bytes(uint64(file.Size)) - } - out[i+1] = fmt.Sprintf("%s|%s|%s|%s", - file.FileMode, - size, - formatTime(file.ModTime), - fn, - ) - } - - f.Ui.Output(formatList(out)) - return 0 -} diff --git a/command/fs_stat.go b/command/fs_stat.go deleted file mode 100644 index 361650b185f0..000000000000 --- a/command/fs_stat.go +++ /dev/null @@ -1,165 +0,0 @@ -package command - -import ( - "fmt" - "strings" - - humanize "github.com/dustin/go-humanize" -) - -type FSStatCommand struct { - Meta -} - -func (f *FSStatCommand) Help() string { - helpText := ` -Usage: nomad fs stat - - Displays information about an entry in an allocation directory at the given path. - The path is relative to the allocation directory and defaults to root if unspecified. - - General Options: - - ` + generalOptionsUsage() + ` - -Stat Options: - - -H - Machine friendly output. - - -verbose - Show full information. - - -job - Use a random allocation from a specified job-id. -` - return strings.TrimSpace(helpText) -} - -func (f *FSStatCommand) Synopsis() string { - return "Stat an entry in an allocation directory" -} - -func (f *FSStatCommand) Run(args []string) int { - var verbose bool - var machine bool - var job bool - flags := f.Meta.FlagSet("fs-list", FlagSetClient) - flags.Usage = func() { f.Ui.Output(f.Help()) } - flags.BoolVar(&verbose, "verbose", false, "") - flags.BoolVar(&machine, "H", false, "") - flags.BoolVar(&job, "job", false, "") - - if err := flags.Parse(args); err != nil { - return 1 - } - args = flags.Args() - - if len(args) < 1 { - f.Ui.Error("allocation id is a required parameter") - return 1 - } - - path := "/" - if len(args) == 2 { - path = args[1] - } - - client, err := f.Meta.Client() - if err != nil { - f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) - return 1 - } - - allocID := args[0] - if job { - allocID, err = getRandomJobAlloc(client, args[0]) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying API: %v", err)) - return 1 - } - } - - // Truncate the id unless full length is requested - length := shortId - if verbose { - length = fullId - } - // Query the allocation info - if len(allocID) == 1 { - f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) - return 1 - } - if len(allocID)%2 == 1 { - // Identifiers must be of even length, so we strip off the last byte - // to provide a consistent user experience. - allocID = allocID[:len(allocID)-1] - } - - allocs, _, err := client.Allocations().PrefixList(allocID) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) - return 1 - } - if len(allocs) == 0 { - f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) - return 1 - } - if len(allocs) > 1 { - // Format the allocs - out := make([]string, len(allocs)+1) - out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" - for i, alloc := range allocs { - out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", - limit(alloc.ID, length), - limit(alloc.EvalID, length), - alloc.JobID, - alloc.TaskGroup, - alloc.DesiredStatus, - alloc.ClientStatus, - ) - } - f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out))) - return 0 - } - // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) - if err != nil { - f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) - return 1 - } - - if alloc.DesiredStatus == "failed" { - allocID := limit(alloc.ID, length) - msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run: -nomad alloc-status %s`, allocID, allocID) - f.Ui.Error(msg) - return 0 - } - // Get the file information - file, _, err := client.AllocFS().Stat(alloc, path, nil) - if err != nil { - f.Ui.Error(err.Error()) - return 1 - } - - // Display the file information - out := make([]string, 2) - out[0] = "Mode|Size|Modified Time|Name" - if file != nil { - fn := file.Name - if file.IsDir { - fn = fmt.Sprintf("%s/", fn) - } - var size string - if machine { - size = fmt.Sprintf("%d", file.Size) - } else { - size = humanize.Bytes(uint64(file.Size)) - } - out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size, - formatTime(file.ModTime), fn) - } - f.Ui.Output(formatList(out)) - return 0 -} diff --git a/commands.go b/commands.go index 2374dea068b7..2c23b0f40475 100644 --- a/commands.go +++ b/commands.go @@ -52,7 +52,6 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "eval-monitor": func() (cli.Command, error) { return &command.EvalMonitorCommand{ Meta: meta, @@ -68,21 +67,6 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "fs ls": func() (cli.Command, error) { - return &command.FSListCommand{ - Meta: meta, - }, nil - }, - "fs stat": func() (cli.Command, error) { - return &command.FSStatCommand{ - Meta: meta, - }, nil - }, - "fs cat": func() (cli.Command, error) { - return &command.FSCatCommand{ - Meta: meta, - }, nil - }, "init": func() (cli.Command, error) { return &command.InitCommand{ Meta: meta, @@ -98,13 +82,11 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "node-status": func() (cli.Command, error) { return &command.NodeStatusCommand{ Meta: meta, }, nil }, - "run": func() (cli.Command, error) { return &command.RunCommand{ Meta: meta, @@ -120,13 +102,11 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "server-join": func() (cli.Command, error) { return &command.ServerJoinCommand{ Meta: meta, }, nil }, - "server-members": func() (cli.Command, error) { return &command.ServerMembersCommand{ Meta: meta, @@ -137,19 +117,16 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, - "stop": func() (cli.Command, error) { return &command.StopCommand{ Meta: meta, }, nil }, - "validate": func() (cli.Command, error) { return &command.ValidateCommand{ Meta: meta, }, nil }, - "version": func() (cli.Command, error) { ver := Version rel := VersionPrerelease diff --git a/website/source/docs/commands/fs.html.md.erb b/website/source/docs/commands/fs.html.md.erb index 64565646736e..aca165d415a3 100644 --- a/website/source/docs/commands/fs.html.md.erb +++ b/website/source/docs/commands/fs.html.md.erb @@ -8,19 +8,18 @@ description: > # Command: fs -The `fs` family of commands allows a user to navigate an allocation directory on a Nomad -client. The following subcommands are available - `cat`, `ls` and `stat` +The `fs` command allows a user to navigate an allocation directory on a Nomad +client. The following functionalities are available - `cat`, `ls` and `stat` -`cat`: Reads contents of files and writes them to the standard output. -`ls`: Displays the name of a file and directories and their associated information. -`stat`: Displays information about a file. +`cat`: If the target path is a file, Nomad will cat the target path. +`ls`: If the target path is a directory, Nomad displays the name of a file and directories and their associated information. +`stat`: If the `-stat` flag is used, Nomad will Display information about a file. ## Usage ``` -nomad fs ls -nomad fs stat -nomad fs cat +nomad fs +nomad fs -stat ``` A valid allocation id is necessary unless `-job` is specified and the path is relative to the root of the allocation directory. @@ -28,25 +27,25 @@ The path is optional and it defaults to `/` of the allocation directory ## Examples -$ nomad fs ls eb17e557 +$ nomad fs eb17e557 Mode Size Modfied Time Name drwxrwxr-x 4096 28 Jan 16 05:39 UTC alloc/ drwxrwxr-x 4096 28 Jan 16 05:39 UTC redis/ -rw-rw-r-- 0 28 Jan 16 05:39 UTC redis_exit_status -$ nomad fs ls redis/local +$ nomad fs redis/local Mode Size Modfied Time Name -rw-rw-rw- 0 28 Jan 16 05:39 UTC redis.stderr -rw-rw-rw- 17 28 Jan 16 05:39 UTC redis.stdout -$ nomad fs stat redis/local/redis.stdout +$ nomad fs -stat redis/local/redis.stdout Mode Size Modified Time Name -rw-rw-rw- 17 28 Jan 16 05:39 UTC redis.stdout -$ nomad fs cat redis/local/redis.stdout +$ nomad fs redis/local/redis.stdout 6710:C 27 Jan 22:04:03.794 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 6710:M 27 Jan 22:04:03.795 * Increased maximum number of open files to 10032 (it was originally set to 256). @@ -55,7 +54,7 @@ $ nomad fs cat redis/local/redis.stdout Passing `-job` into one of the `fs` commands will allow the `fs` command to randomly select an allocation ID from the specified job. ``` -nomad fs ls -job +nomad fs -job ``` Nomad will prefer to select a running allocation ID for the job, but if no running allocations for the job are found, Nomad will use a dead allocation.