github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/job_validate.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/command/agent" 11 "github.com/hashicorp/nomad/helper/pointer" 12 "github.com/posener/complete" 13 ) 14 15 type JobValidateCommand struct { 16 Meta 17 JobGetter 18 } 19 20 func (c *JobValidateCommand) Help() string { 21 helpText := ` 22 Usage: nomad job validate [options] <path> 23 Alias: nomad validate 24 25 Checks if a given HCL job file has a valid specification. This can be used to 26 check for any syntax errors or validation problems with a job. 27 28 If the supplied path is "-", the jobfile is read from stdin. Otherwise 29 it is read from the file at the supplied path or downloaded and 30 read from URL specified. 31 32 The run command will set the vault_token of the job based on the following 33 precedence, going from highest to lowest: the -vault-token flag, the 34 $VAULT_TOKEN environment variable and finally the value in the job file. 35 36 When ACLs are enabled, this command requires a token with the 'read-job' 37 capability for the job's namespace. 38 39 General Options: 40 41 ` + generalOptionsUsage(usageOptsDefault) + ` 42 43 Validate Options: 44 45 -json 46 Parses the job file as JSON. If the outer object has a Job field, such as 47 from "nomad job inspect" or "nomad run -output", the value of the field is 48 used as the job. 49 50 -hcl1 51 Parses the job file as HCLv1. Takes precedence over "-hcl2-strict". 52 53 -hcl2-strict 54 Whether an error should be produced from the HCL2 parser where a variable 55 has been supplied which is not defined within the root variables. Defaults 56 to true, but ignored if "-hcl1" is also defined. 57 58 -vault-token 59 Used to validate if the user submitting the job has permission to run the job 60 according to its Vault policies. A Vault token must be supplied if the vault 61 stanza allow_unauthenticated is disabled in the Nomad server configuration. 62 If the -vault-token flag is set, the passed Vault token is added to the jobspec 63 before sending to the Nomad servers. This allows passing the Vault token 64 without storing it in the job file. This overrides the token found in the 65 $VAULT_TOKEN environment variable and the vault_token field in the job file. 66 This token is cleared from the job after validating and cannot be used within 67 the job executing environment. Use the vault stanza when templating in a job 68 with a Vault token. 69 70 -vault-namespace 71 If set, the passed Vault namespace is stored in the job before sending to the 72 Nomad servers. 73 74 -var 'key=value' 75 Variable for template, can be used multiple times. 76 77 -var-file=path 78 Path to HCL2 file containing user variables. 79 ` 80 return strings.TrimSpace(helpText) 81 } 82 83 func (c *JobValidateCommand) Synopsis() string { 84 return "Checks if a given job specification is valid" 85 } 86 87 func (c *JobValidateCommand) AutocompleteFlags() complete.Flags { 88 return complete.Flags{ 89 "-hcl1": complete.PredictNothing, 90 "-hcl2-strict": complete.PredictNothing, 91 "-vault-token": complete.PredictAnything, 92 "-vault-namespace": complete.PredictAnything, 93 "-var": complete.PredictAnything, 94 "-var-file": complete.PredictFiles("*.var"), 95 } 96 } 97 98 func (c *JobValidateCommand) AutocompleteArgs() complete.Predictor { 99 return complete.PredictOr( 100 complete.PredictFiles("*.nomad"), 101 complete.PredictFiles("*.hcl"), 102 complete.PredictFiles("*.json"), 103 ) 104 } 105 106 func (c *JobValidateCommand) Name() string { return "job validate" } 107 108 func (c *JobValidateCommand) Run(args []string) int { 109 var vaultToken, vaultNamespace string 110 111 flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient) 112 flagSet.Usage = func() { c.Ui.Output(c.Help()) } 113 flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "") 114 flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "") 115 flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "") 116 flagSet.StringVar(&vaultToken, "vault-token", "", "") 117 flagSet.StringVar(&vaultNamespace, "vault-namespace", "", "") 118 flagSet.Var(&c.JobGetter.Vars, "var", "") 119 flagSet.Var(&c.JobGetter.VarFiles, "var-file", "") 120 121 if err := flagSet.Parse(args); err != nil { 122 return 1 123 } 124 125 // Check that we got exactly one node 126 args = flagSet.Args() 127 if len(args) != 1 { 128 c.Ui.Error("This command takes one argument: <path>") 129 c.Ui.Error(commandErrorText(c)) 130 return 1 131 } 132 133 if c.JobGetter.HCL1 { 134 c.JobGetter.Strict = false 135 } 136 137 if err := c.JobGetter.Validate(); err != nil { 138 c.Ui.Error(fmt.Sprintf("Invalid job options: %s", err)) 139 return 1 140 } 141 142 // Get Job struct from Jobfile 143 job, err := c.JobGetter.Get(args[0]) 144 if err != nil { 145 c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err)) 146 return 1 147 } 148 149 // Get the HTTP client 150 client, err := c.Meta.Client() 151 if err != nil { 152 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 153 return 255 154 } 155 156 // Force the region to be that of the job. 157 if r := job.Region; r != nil { 158 client.SetRegion(*r) 159 } 160 161 // Parse the Vault token 162 if vaultToken == "" { 163 // Check the environment variable 164 vaultToken = os.Getenv("VAULT_TOKEN") 165 } 166 167 if vaultToken != "" { 168 job.VaultToken = pointer.Of(vaultToken) 169 } 170 171 if vaultNamespace != "" { 172 job.VaultNamespace = pointer.Of(vaultNamespace) 173 } 174 175 // Check that the job is valid 176 jr, _, err := client.Jobs().Validate(job, nil) 177 if err != nil { 178 jr, err = c.validateLocal(job) 179 } 180 if err != nil { 181 c.Ui.Error(fmt.Sprintf("Error validating job: %s", err)) 182 return 1 183 } 184 185 if jr != nil && !jr.DriverConfigValidated { 186 c.Ui.Output( 187 c.Colorize().Color("[bold][yellow]Driver configuration not validated since connection to Nomad agent couldn't be established.[reset]\n")) 188 } 189 190 if jr != nil && jr.Error != "" { 191 c.Ui.Error( 192 c.Colorize().Color("[bold][red]Job validation errors:[reset]")) 193 c.Ui.Error(jr.Error) 194 return 1 195 } 196 197 // Print any warnings if there are any 198 if jr.Warnings != "" { 199 c.Ui.Output( 200 c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", jr.Warnings))) 201 } 202 203 // Done! 204 c.Ui.Output( 205 c.Colorize().Color("[bold][green]Job validation successful[reset]")) 206 return 0 207 } 208 209 // validateLocal validates without talking to a Nomad agent 210 func (c *JobValidateCommand) validateLocal(aj *api.Job) (*api.JobValidateResponse, error) { 211 var out api.JobValidateResponse 212 213 job := agent.ApiJobToStructJob(aj) 214 job.Canonicalize() 215 216 if vErr := job.Validate(); vErr != nil { 217 if merr, ok := vErr.(*multierror.Error); ok { 218 for _, err := range merr.Errors { 219 out.ValidationErrors = append(out.ValidationErrors, err.Error()) 220 } 221 out.Error = merr.Error() 222 } else { 223 out.ValidationErrors = append(out.ValidationErrors, vErr.Error()) 224 out.Error = vErr.Error() 225 } 226 } 227 228 out.Warnings = mergeMultierrorWarnings(job.Warnings()) 229 return &out, nil 230 } 231 232 func mergeMultierrorWarnings(errs ...error) string { 233 if len(errs) == 0 { 234 return "" 235 } 236 237 var mErr multierror.Error 238 _ = multierror.Append(&mErr, errs...) 239 240 mErr.ErrorFormat = warningsFormatter 241 242 return mErr.Error() 243 } 244 245 func warningsFormatter(es []error) string { 246 sb := strings.Builder{} 247 sb.WriteString(fmt.Sprintf("%d warning(s):\n", len(es))) 248 249 for i := range es { 250 sb.WriteString(fmt.Sprintf("\n* %s", es[i])) 251 } 252 253 return sb.String() 254 }