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