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  }