github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/command/helpers.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "strconv" 10 "time" 11 12 gg "github.com/hashicorp/go-getter" 13 "github.com/hashicorp/nomad/api" 14 "github.com/hashicorp/nomad/jobspec" 15 16 "github.com/ryanuber/columnize" 17 ) 18 19 // formatKV takes a set of strings and formats them into properly 20 // aligned k = v pairs using the columnize library. 21 func formatKV(in []string) string { 22 columnConf := columnize.DefaultConfig() 23 columnConf.Empty = "<none>" 24 columnConf.Glue = " = " 25 return columnize.Format(in, columnConf) 26 } 27 28 // formatList takes a set of strings and formats them into properly 29 // aligned output, replacing any blank fields with a placeholder 30 // for awk-ability. 31 func formatList(in []string) string { 32 columnConf := columnize.DefaultConfig() 33 columnConf.Empty = "<none>" 34 return columnize.Format(in, columnConf) 35 } 36 37 // formatListWithSpaces takes a set of strings and formats them into properly 38 // aligned output. It should be used sparingly since it doesn't replace empty 39 // values and hence not awk/sed friendly 40 func formatListWithSpaces(in []string) string { 41 columnConf := columnize.DefaultConfig() 42 return columnize.Format(in, columnConf) 43 } 44 45 // Limits the length of the string. 46 func limit(s string, length int) string { 47 if len(s) < length { 48 return s 49 } 50 51 return s[:length] 52 } 53 54 // formatTime formats the time to string based on RFC822 55 func formatTime(t time.Time) string { 56 return t.Format("01/02/06 15:04:05 MST") 57 } 58 59 // formatUnixNanoTime is a helper for formatting time for output. 60 func formatUnixNanoTime(nano int64) string { 61 t := time.Unix(0, nano) 62 return formatTime(t) 63 } 64 65 // formatTimeDifference takes two times and determines their duration difference 66 // truncating to a passed unit. 67 // E.g. formatTimeDifference(first=1m22s33ms, second=1m28s55ms, time.Second) -> 6s 68 func formatTimeDifference(first, second time.Time, d time.Duration) string { 69 return second.Truncate(d).Sub(first.Truncate(d)).String() 70 } 71 72 // getLocalNodeID returns the node ID of the local Nomad Client and an error if 73 // it couldn't be determined or the Agent is not running in Client mode. 74 func getLocalNodeID(client *api.Client) (string, error) { 75 info, err := client.Agent().Self() 76 if err != nil { 77 return "", fmt.Errorf("Error querying agent info: %s", err) 78 } 79 clientStats, ok := info.Stats["client"] 80 if !ok { 81 return "", fmt.Errorf("Nomad not running in client mode") 82 } 83 84 nodeID, ok := clientStats["node_id"] 85 if !ok { 86 return "", fmt.Errorf("Failed to determine node ID") 87 } 88 89 return nodeID, nil 90 } 91 92 // evalFailureStatus returns whether the evaluation has failures and a string to 93 // display when presenting users with whether there are failures for the eval 94 func evalFailureStatus(eval *api.Evaluation) (string, bool) { 95 if eval == nil { 96 return "", false 97 } 98 99 hasFailures := len(eval.FailedTGAllocs) != 0 100 text := strconv.FormatBool(hasFailures) 101 if eval.Status == "blocked" { 102 text = "N/A - In Progress" 103 } 104 105 return text, hasFailures 106 } 107 108 // LineLimitReader wraps another reader and provides `tail -n` like behavior. 109 // LineLimitReader buffers up to the searchLimit and returns `-n` number of 110 // lines. After those lines have been returned, LineLimitReader streams the 111 // underlying ReadCloser 112 type LineLimitReader struct { 113 io.ReadCloser 114 lines int 115 searchLimit int 116 117 timeLimit time.Duration 118 lastRead time.Time 119 120 buffer *bytes.Buffer 121 bufFiled bool 122 foundLines bool 123 } 124 125 // NewLineLimitReader takes the ReadCloser to wrap, the number of lines to find 126 // searching backwards in the first searchLimit bytes. timeLimit can optionally 127 // be specified by passing a non-zero duration. When set, the search for the 128 // last n lines is aborted if no data has been read in the duration. This 129 // can be used to flush what is had if no extra data is being received. When 130 // used, the underlying reader must not block forever and must periodically 131 // unblock even when no data has been read. 132 func NewLineLimitReader(r io.ReadCloser, lines, searchLimit int, timeLimit time.Duration) *LineLimitReader { 133 return &LineLimitReader{ 134 ReadCloser: r, 135 searchLimit: searchLimit, 136 timeLimit: timeLimit, 137 lines: lines, 138 buffer: bytes.NewBuffer(make([]byte, 0, searchLimit)), 139 } 140 } 141 142 func (l *LineLimitReader) Read(p []byte) (n int, err error) { 143 // Fill up the buffer so we can find the correct number of lines. 144 if !l.bufFiled { 145 b := make([]byte, len(p)) 146 n, err := l.ReadCloser.Read(b) 147 if n > 0 { 148 if _, err := l.buffer.Write(b[:n]); err != nil { 149 return 0, err 150 } 151 } 152 153 if err != nil { 154 if err != io.EOF { 155 return 0, err 156 } 157 158 l.bufFiled = true 159 goto READ 160 } 161 162 if l.buffer.Len() >= l.searchLimit { 163 l.bufFiled = true 164 goto READ 165 } 166 167 if l.timeLimit.Nanoseconds() > 0 { 168 if l.lastRead.IsZero() { 169 l.lastRead = time.Now() 170 return 0, nil 171 } 172 173 now := time.Now() 174 if n == 0 { 175 // We hit the limit 176 if l.lastRead.Add(l.timeLimit).Before(now) { 177 l.bufFiled = true 178 goto READ 179 } else { 180 return 0, nil 181 } 182 } else { 183 l.lastRead = now 184 } 185 } 186 187 return 0, nil 188 } 189 190 READ: 191 if l.bufFiled && l.buffer.Len() != 0 { 192 b := l.buffer.Bytes() 193 194 // Find the lines 195 if !l.foundLines { 196 found := 0 197 i := len(b) - 1 198 sep := byte('\n') 199 lastIndex := len(b) - 1 200 for ; found < l.lines && i >= 0; i-- { 201 if b[i] == sep { 202 lastIndex = i 203 204 // Skip the first one 205 if i != len(b)-1 { 206 found++ 207 } 208 } 209 } 210 211 // We found them all 212 if found == l.lines { 213 // Clear the buffer until the last index 214 l.buffer.Next(lastIndex + 1) 215 } 216 217 l.foundLines = true 218 } 219 220 // Read from the buffer 221 n := copy(p, l.buffer.Next(len(p))) 222 return n, nil 223 } 224 225 // Just stream from the underlying reader now 226 return l.ReadCloser.Read(p) 227 } 228 229 type JobGetter struct { 230 // The fields below can be overwritten for tests 231 testStdin io.Reader 232 } 233 234 // StructJob returns the Job struct from jobfile. 235 func (j *JobGetter) ApiJob(jpath string) (*api.Job, error) { 236 var jobfile io.Reader 237 switch jpath { 238 case "-": 239 if j.testStdin != nil { 240 jobfile = j.testStdin 241 } else { 242 jobfile = os.Stdin 243 } 244 default: 245 if len(jpath) == 0 { 246 return nil, fmt.Errorf("Error jobfile path has to be specified.") 247 } 248 249 job, err := ioutil.TempFile("", "jobfile") 250 if err != nil { 251 return nil, err 252 } 253 defer os.Remove(job.Name()) 254 255 if err := job.Close(); err != nil { 256 return nil, err 257 } 258 259 // Get the pwd 260 pwd, err := os.Getwd() 261 if err != nil { 262 return nil, err 263 } 264 265 client := &gg.Client{ 266 Src: jpath, 267 Pwd: pwd, 268 Dst: job.Name(), 269 } 270 271 if err := client.Get(); err != nil { 272 return nil, fmt.Errorf("Error getting jobfile from %q: %v", jpath, err) 273 } else { 274 file, err := os.Open(job.Name()) 275 defer file.Close() 276 if err != nil { 277 return nil, fmt.Errorf("Error opening file %q: %v", jpath, err) 278 } 279 jobfile = file 280 } 281 } 282 283 // Parse the JobFile 284 jobStruct, err := jobspec.Parse(jobfile) 285 if err != nil { 286 return nil, fmt.Errorf("Error parsing job file from %s: %v", jpath, err) 287 } 288 289 return jobStruct, nil 290 }