github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/command/job_history.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/ryanuber/columnize"
    11  )
    12  
    13  type JobHistoryCommand struct {
    14  	Meta
    15  	formatter DataFormatter
    16  }
    17  
    18  func (c *JobHistoryCommand) Help() string {
    19  	helpText := `
    20  Usage: nomad job history [options] <job>
    21  
    22  History is used to display the known versions of a particular job. The command
    23  can display the diff between job versions and can be useful for understanding
    24  the changes that occured to the job as well as deciding job versions to revert
    25  to.
    26  
    27  General Options:
    28  
    29    ` + generalOptionsUsage() + `
    30  
    31  History Options:
    32  
    33    -p
    34      Display the difference between each job and its predecessor.
    35      
    36    -full
    37      Display the full job definition for each version.
    38  
    39    -version <job version>
    40      Display only the history for the given job version.
    41  
    42    -json
    43      Output the job versions in a JSON format.
    44  
    45    -t
    46      Format and display the job versions using a Go template.
    47  `
    48  	return strings.TrimSpace(helpText)
    49  }
    50  
    51  func (c *JobHistoryCommand) Synopsis() string {
    52  	return "Display all tracked versions of a job"
    53  }
    54  
    55  func (c *JobHistoryCommand) Run(args []string) int {
    56  	var json, diff, full bool
    57  	var tmpl, versionStr string
    58  
    59  	flags := c.Meta.FlagSet("job history", FlagSetClient)
    60  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    61  	flags.BoolVar(&diff, "p", false, "")
    62  	flags.BoolVar(&full, "full", false, "")
    63  	flags.BoolVar(&json, "json", false, "")
    64  	flags.StringVar(&versionStr, "version", "", "")
    65  	flags.StringVar(&tmpl, "t", "", "")
    66  
    67  	if err := flags.Parse(args); err != nil {
    68  		return 1
    69  	}
    70  
    71  	// Check that we got exactly one node
    72  	args = flags.Args()
    73  	if l := len(args); l < 1 || l > 2 {
    74  		c.Ui.Error(c.Help())
    75  		return 1
    76  	}
    77  
    78  	if (json || len(tmpl) != 0) && (diff || full) {
    79  		c.Ui.Error("-json and -t are exclusive with -p and -full")
    80  		return 1
    81  	}
    82  
    83  	// Get the HTTP client
    84  	client, err := c.Meta.Client()
    85  	if err != nil {
    86  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    87  		return 1
    88  	}
    89  
    90  	jobID := args[0]
    91  
    92  	// Check if the job exists
    93  	jobs, _, err := client.Jobs().PrefixList(jobID)
    94  	if err != nil {
    95  		c.Ui.Error(fmt.Sprintf("Error listing jobs: %s", err))
    96  		return 1
    97  	}
    98  	if len(jobs) == 0 {
    99  		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
   100  		return 1
   101  	}
   102  	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
   103  		c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
   104  		return 1
   105  	}
   106  
   107  	// Prefix lookup matched a single job
   108  	versions, diffs, _, err := client.Jobs().Versions(jobs[0].ID, diff, nil)
   109  	if err != nil {
   110  		c.Ui.Error(fmt.Sprintf("Error retrieving job versions: %s", err))
   111  		return 1
   112  	}
   113  
   114  	f, err := DataFormat("json", "")
   115  	if err != nil {
   116  		c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err))
   117  		return 1
   118  	}
   119  	c.formatter = f
   120  
   121  	if versionStr != "" {
   122  		version, _, err := parseVersion(versionStr)
   123  		if err != nil {
   124  			c.Ui.Error(fmt.Sprintf("Error parsing version value %q: %v", versionStr, err))
   125  			return 1
   126  		}
   127  
   128  		var job *api.Job
   129  		var diff *api.JobDiff
   130  		var nextVersion uint64
   131  		for i, v := range versions {
   132  			if *v.Version != version {
   133  				continue
   134  			}
   135  
   136  			job = v
   137  			if i+1 <= len(diffs) {
   138  				diff = diffs[i]
   139  				nextVersion = *versions[i+1].Version
   140  			}
   141  		}
   142  
   143  		if json || len(tmpl) > 0 {
   144  			out, err := Format(json, tmpl, job)
   145  			if err != nil {
   146  				c.Ui.Error(err.Error())
   147  				return 1
   148  			}
   149  
   150  			c.Ui.Output(out)
   151  			return 0
   152  		}
   153  
   154  		if err := c.formatJobVersion(job, diff, nextVersion, full); err != nil {
   155  			c.Ui.Error(err.Error())
   156  			return 1
   157  		}
   158  
   159  	} else {
   160  		if json || len(tmpl) > 0 {
   161  			out, err := Format(json, tmpl, versions)
   162  			if err != nil {
   163  				c.Ui.Error(err.Error())
   164  				return 1
   165  			}
   166  
   167  			c.Ui.Output(out)
   168  			return 0
   169  		}
   170  
   171  		if err := c.formatJobVersions(versions, diffs, full); err != nil {
   172  			c.Ui.Error(err.Error())
   173  			return 1
   174  		}
   175  	}
   176  
   177  	return 0
   178  }
   179  
   180  // parseVersion parses the version flag and returns the index, whether it
   181  // was set and potentially an error during parsing.
   182  func parseVersion(input string) (uint64, bool, error) {
   183  	if input == "" {
   184  		return 0, false, nil
   185  	}
   186  
   187  	u, err := strconv.ParseUint(input, 10, 64)
   188  	return u, true, err
   189  }
   190  
   191  func (c *JobHistoryCommand) formatJobVersions(versions []*api.Job, diffs []*api.JobDiff, full bool) error {
   192  	vLen := len(versions)
   193  	dLen := len(diffs)
   194  	if dLen != 0 && vLen != dLen+1 {
   195  		return fmt.Errorf("Number of job versions %d doesn't match number of diffs %d", vLen, dLen)
   196  	}
   197  
   198  	for i, version := range versions {
   199  		var diff *api.JobDiff
   200  		var nextVersion uint64
   201  		if i+1 <= dLen {
   202  			diff = diffs[i]
   203  			nextVersion = *versions[i+1].Version
   204  		}
   205  
   206  		if err := c.formatJobVersion(version, diff, nextVersion, full); err != nil {
   207  			return err
   208  		}
   209  
   210  		// Insert a blank
   211  		if i != vLen-1 {
   212  			c.Ui.Output("")
   213  		}
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  func (c *JobHistoryCommand) formatJobVersion(job *api.Job, diff *api.JobDiff, nextVersion uint64, full bool) error {
   220  	basic := []string{
   221  		fmt.Sprintf("Version|%d", *job.Version),
   222  		fmt.Sprintf("Stable|%v", *job.Stable),
   223  		fmt.Sprintf("Submit Date|%v", formatTime(time.Unix(0, *job.SubmitTime))),
   224  	}
   225  
   226  	if diff != nil {
   227  		//diffStr := fmt.Sprintf("Difference between version %d and %d:", *job.Version, nextVersion)
   228  		basic = append(basic, fmt.Sprintf("Diff|\n%s", strings.TrimSpace(formatJobDiff(diff, false))))
   229  	}
   230  
   231  	if full {
   232  		out, err := c.formatter.TransformData(job)
   233  		if err != nil {
   234  			return fmt.Errorf("Error formatting the data: %s", err)
   235  		}
   236  
   237  		basic = append(basic, fmt.Sprintf("Full|JSON Job:\n%s", out))
   238  	}
   239  
   240  	columnConf := columnize.DefaultConfig()
   241  	columnConf.Glue = " = "
   242  	columnConf.NoTrim = true
   243  	output := columnize.Format(basic, columnConf)
   244  
   245  	c.Ui.Output(c.Colorize().Color(output))
   246  	return nil
   247  }