github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/e2e/e2eutil/cli.go (about)

     1  package e2eutil
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"regexp"
     7  	"strings"
     8  )
     9  
    10  // Command sends a command line argument to Nomad and returns the unbuffered
    11  // stdout as a string (or, if there's an error, the stderr)
    12  func Command(cmd string, args ...string) (string, error) {
    13  	bytes, err := exec.Command(cmd, args...).CombinedOutput()
    14  	out := string(bytes)
    15  	if err != nil {
    16  		return out, fmt.Errorf("command %v %v failed: %v\nOutput: %v", cmd, args, err, out)
    17  	}
    18  	return out, err
    19  }
    20  
    21  // GetField returns the value of an output field (ex. the "Submit Date" field
    22  // of `nomad job status :id`)
    23  func GetField(output, key string) (string, error) {
    24  	re := regexp.MustCompile(`(?m)^` + key + ` += (.*)$`)
    25  	match := re.FindStringSubmatch(output)
    26  	if match == nil {
    27  		return "", fmt.Errorf("could not find field %q", key)
    28  	}
    29  	return match[1], nil
    30  }
    31  
    32  // GetSection returns a section, with its field header but without its title.
    33  // (ex. the Allocations section of `nomad job status :id`)
    34  func GetSection(output, key string) (string, error) {
    35  
    36  	// golang's regex engine doesn't support negative lookahead, so
    37  	// we can't stop at 2 newlines if we also want a section that includes
    38  	// single newlines. so split on the section title, and then split a second time
    39  	// on \n\n
    40  	re := regexp.MustCompile(`(?ms)^` + key + `\n(.*)`)
    41  	match := re.FindStringSubmatch(output)
    42  	if match == nil {
    43  		return "", fmt.Errorf("could not find section %q", key)
    44  	}
    45  	tail := match[1]
    46  	return strings.Split(tail, "\n\n")[0], nil
    47  }
    48  
    49  // ParseColumns maps the CLI output for a columized section (without title) to
    50  // a slice of key->value pairs for each row in that section.
    51  // (ex. the Allocations section of `nomad job status :id`)
    52  func ParseColumns(section string) ([]map[string]string, error) {
    53  	parsed := []map[string]string{}
    54  
    55  	// field names and values are deliminated by two or more spaces, but can have a
    56  	// single space themselves. compress all the delimiters into a tab so we can
    57  	// break the fields on that
    58  	re := regexp.MustCompile(" {2,}")
    59  	section = re.ReplaceAllString(section, "\t")
    60  	rows := strings.Split(section, "\n")
    61  
    62  	breakFields := func(row string) []string {
    63  		return strings.FieldsFunc(row, func(c rune) bool { return c == '\t' })
    64  	}
    65  
    66  	fieldNames := breakFields(rows[0])
    67  
    68  	for _, row := range rows[1:] {
    69  		if row == "" {
    70  			continue
    71  		}
    72  		r := map[string]string{}
    73  		vals := breakFields(row)
    74  		for i, val := range vals {
    75  			if i >= len(fieldNames) {
    76  				return parsed, fmt.Errorf("section is misaligned with header\n%v", section)
    77  			}
    78  
    79  			r[fieldNames[i]] = val
    80  		}
    81  		parsed = append(parsed, r)
    82  	}
    83  	return parsed, nil
    84  }
    85  
    86  // ParseFields maps the CLI output for a key-value section (without title) to
    87  // map of the key->value pairs in that section
    88  // (ex. the Latest Deployment section of `nomad job status :id`)
    89  func ParseFields(section string) (map[string]string, error) {
    90  	parsed := map[string]string{}
    91  	rows := strings.Split(strings.TrimSpace(section), "\n")
    92  	for _, row := range rows {
    93  		kv := strings.Split(row, "=")
    94  		if len(kv) == 0 {
    95  			continue
    96  		}
    97  		key := strings.TrimSpace(kv[0])
    98  		if len(kv) == 1 {
    99  			parsed[key] = ""
   100  		} else {
   101  			parsed[key] = strings.TrimSpace(strings.Join(kv[1:], " "))
   102  		}
   103  	}
   104  	return parsed, nil
   105  }