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 }