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