github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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 }