github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/fs.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"math/rand"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	humanize "github.com/dustin/go-humanize"
    12  	"github.com/hashicorp/nomad/api"
    13  )
    14  
    15  type FSCommand struct {
    16  	Meta
    17  }
    18  
    19  func (f *FSCommand) Help() string {
    20  	helpText := `
    21  Usage: nomad fs <alloc-id> <path>
    22  
    23    fs displays either the contents of an allocation directory for the passed allocation,
    24    or displays the file at the given path. The path is relative to the root of the alloc
    25    dir and defaults to root if unspecified.
    26  
    27  General Options:
    28  
    29    ` + generalOptionsUsage() + `
    30  
    31    -H
    32      Machine friendly output.
    33  
    34    -verbose
    35      Show full information.
    36  
    37    -job <job-id>
    38      Use a random allocation from a specified job-id.
    39  
    40    -stat
    41      Show file stat information instead of displaying the file, or listing the directory.
    42  
    43  `
    44  	return strings.TrimSpace(helpText)
    45  }
    46  
    47  func (f *FSCommand) Synopsis() string {
    48  	return "Inspect the contents of an allocation directory"
    49  }
    50  
    51  func (f *FSCommand) Run(args []string) int {
    52  	var verbose, machine, job, stat bool
    53  	flags := f.Meta.FlagSet("fs-list", FlagSetClient)
    54  	flags.Usage = func() { f.Ui.Output(f.Help()) }
    55  	flags.BoolVar(&verbose, "verbose", false, "")
    56  	flags.BoolVar(&machine, "H", false, "")
    57  	flags.BoolVar(&job, "job", false, "")
    58  	flags.BoolVar(&stat, "stat", false, "")
    59  
    60  	if err := flags.Parse(args); err != nil {
    61  		return 1
    62  	}
    63  	args = flags.Args()
    64  
    65  	if len(args) < 1 {
    66  		if job {
    67  			f.Ui.Error("job ID is required")
    68  		} else {
    69  			f.Ui.Error("allocation ID is required")
    70  		}
    71  
    72  		return 1
    73  	}
    74  
    75  	path := "/"
    76  	if len(args) == 2 {
    77  		path = args[1]
    78  	}
    79  
    80  	client, err := f.Meta.Client()
    81  	if err != nil {
    82  		f.Ui.Error(fmt.Sprintf("Error initializing client: %v", err))
    83  		return 1
    84  	}
    85  
    86  	// If -job is specified, use random allocation, otherwise use provided allocation
    87  	allocID := args[0]
    88  	if job {
    89  		allocID, err = getRandomJobAlloc(client, args[0])
    90  		if err != nil {
    91  			f.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err))
    92  			return 1
    93  		}
    94  	}
    95  
    96  	// Truncate the id unless full length is requested
    97  	length := shortId
    98  	if verbose {
    99  		length = fullId
   100  	}
   101  	// Query the allocation info
   102  	if len(allocID) == 1 {
   103  		f.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters."))
   104  		return 1
   105  	}
   106  	if len(allocID)%2 == 1 {
   107  		// Identifiers must be of even length, so we strip off the last byte
   108  		// to provide a consistent user experience.
   109  		allocID = allocID[:len(allocID)-1]
   110  	}
   111  
   112  	allocs, _, err := client.Allocations().PrefixList(allocID)
   113  	if err != nil {
   114  		f.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err))
   115  		return 1
   116  	}
   117  	if len(allocs) == 0 {
   118  		f.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID))
   119  		return 1
   120  	}
   121  	if len(allocs) > 1 {
   122  		// Format the allocs
   123  		out := make([]string, len(allocs)+1)
   124  		out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status"
   125  		for i, alloc := range allocs {
   126  			out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s",
   127  				limit(alloc.ID, length),
   128  				limit(alloc.EvalID, length),
   129  				alloc.JobID,
   130  				alloc.TaskGroup,
   131  				alloc.DesiredStatus,
   132  				alloc.ClientStatus,
   133  			)
   134  		}
   135  		f.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out)))
   136  		return 0
   137  	}
   138  	// Prefix lookup matched a single allocation
   139  	alloc, _, err := client.Allocations().Info(allocs[0].ID, nil)
   140  	if err != nil {
   141  		f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err))
   142  		return 1
   143  	}
   144  
   145  	if alloc.DesiredStatus == "failed" {
   146  		allocID := limit(alloc.ID, length)
   147  		msg := fmt.Sprintf(`The allocation %q failed to be placed. To see the cause, run:
   148  nomad alloc-status %s`, allocID, allocID)
   149  		f.Ui.Error(msg)
   150  		return 0
   151  	}
   152  
   153  	// Get file stat info
   154  	file, _, err := client.AllocFS().Stat(alloc, path, nil)
   155  	if err != nil {
   156  		f.Ui.Error(err.Error())
   157  		return 1
   158  	}
   159  
   160  	// If we want file stats, print those and exit.
   161  	if stat {
   162  		// Display the file information
   163  		out := make([]string, 2)
   164  		out[0] = "Mode|Size|Modified Time|Name"
   165  		if file != nil {
   166  			fn := file.Name
   167  			if file.IsDir {
   168  				fn = fmt.Sprintf("%s/", fn)
   169  			}
   170  			var size string
   171  			if machine {
   172  				size = fmt.Sprintf("%d", file.Size)
   173  			} else {
   174  				size = humanize.Bytes(uint64(file.Size))
   175  			}
   176  			out[1] = fmt.Sprintf("%s|%s|%s|%s", file.FileMode, size,
   177  				formatTime(file.ModTime), fn)
   178  		}
   179  		f.Ui.Output(formatList(out))
   180  		return 0
   181  	}
   182  
   183  	// Determine if the path is a file or a directory.
   184  	if file.IsDir {
   185  		// We have a directory, list it.
   186  		files, _, err := client.AllocFS().List(alloc, path, nil)
   187  		if err != nil {
   188  			f.Ui.Error(fmt.Sprintf("Error listing alloc dir: %s", err))
   189  			return 1
   190  		}
   191  		// Display the file information in a tabular format
   192  		out := make([]string, len(files)+1)
   193  		out[0] = "Mode|Size|Modfied Time|Name"
   194  		for i, file := range files {
   195  			fn := file.Name
   196  			if file.IsDir {
   197  				fn = fmt.Sprintf("%s/", fn)
   198  			}
   199  			var size string
   200  			if machine {
   201  				size = fmt.Sprintf("%d", file.Size)
   202  			} else {
   203  				size = humanize.Bytes(uint64(file.Size))
   204  			}
   205  			out[i+1] = fmt.Sprintf("%s|%s|%s|%s",
   206  				file.FileMode,
   207  				size,
   208  				formatTime(file.ModTime),
   209  				fn,
   210  			)
   211  		}
   212  		f.Ui.Output(formatList(out))
   213  	} else {
   214  		// We have a file, cat it.
   215  		r, _, err := client.AllocFS().Cat(alloc, path, nil)
   216  		if err != nil {
   217  			f.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
   218  			return 1
   219  		}
   220  		io.Copy(os.Stdout, r)
   221  	}
   222  
   223  	return 0
   224  }
   225  
   226  // Get Random Allocation ID from a known jobID. Prefer to use a running allocation,
   227  // but use a dead allocation if no running allocations are found
   228  func getRandomJobAlloc(client *api.Client, jobID string) (string, error) {
   229  	var runningAllocs []*api.AllocationListStub
   230  	allocs, _, err := client.Jobs().Allocations(jobID, nil)
   231  
   232  	// Check that the job actually has allocations
   233  	if len(allocs) == 0 {
   234  		return "", fmt.Errorf("job %q doesn't exist or it has no allocations", jobID)
   235  	}
   236  
   237  	for _, v := range allocs {
   238  		if v.ClientStatus == "running" {
   239  			runningAllocs = append(runningAllocs, v)
   240  		}
   241  	}
   242  	// If we don't have any allocations running, use dead allocations
   243  	if len(runningAllocs) < 1 {
   244  		runningAllocs = allocs
   245  	}
   246  
   247  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   248  	allocID := runningAllocs[r.Intn(len(runningAllocs))].ID
   249  	return allocID, err
   250  }