1package command
2
3import (
4	"fmt"
5	"strings"
6
7	multierror "github.com/hashicorp/go-multierror"
8	"github.com/hashicorp/nomad/api"
9	"github.com/hashicorp/nomad/command/agent"
10	"github.com/hashicorp/nomad/nomad/structs"
11	"github.com/posener/complete"
12)
13
14type JobValidateCommand struct {
15	Meta
16	JobGetter
17}
18
19func (c *JobValidateCommand) Help() string {
20	helpText := `
21Usage: nomad job validate [options] <path>
22Alias: nomad validate
23
24  Checks if a given HCL job file has a valid specification. This can be used to
25  check for any syntax errors or validation problems with a job.
26
27  If the supplied path is "-", the jobfile is read from stdin. Otherwise
28  it is read from the file at the supplied path or downloaded and
29  read from URL specified.
30`
31	return strings.TrimSpace(helpText)
32}
33
34func (c *JobValidateCommand) Synopsis() string {
35	return "Checks if a given job specification is valid"
36}
37
38func (c *JobValidateCommand) AutocompleteFlags() complete.Flags {
39	return nil
40}
41
42func (c *JobValidateCommand) AutocompleteArgs() complete.Predictor {
43	return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl"))
44}
45
46func (c *JobValidateCommand) Name() string { return "job validate" }
47
48func (c *JobValidateCommand) Run(args []string) int {
49	flags := c.Meta.FlagSet(c.Name(), FlagSetNone)
50	flags.Usage = func() { c.Ui.Output(c.Help()) }
51	if err := flags.Parse(args); err != nil {
52		return 1
53	}
54
55	// Check that we got exactly one node
56	args = flags.Args()
57	if len(args) != 1 {
58		c.Ui.Error("This command takes one argument: <path>")
59		c.Ui.Error(commandErrorText(c))
60		return 1
61	}
62
63	// Get Job struct from Jobfile
64	job, err := c.JobGetter.ApiJob(args[0])
65	if err != nil {
66		c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
67		return 1
68	}
69
70	// Get the HTTP client
71	client, err := c.Meta.Client()
72	if err != nil {
73		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
74		return 255
75	}
76
77	// Force the region to be that of the job.
78	if r := job.Region; r != nil {
79		client.SetRegion(*r)
80	}
81
82	// Check that the job is valid
83	jr, _, err := client.Jobs().Validate(job, nil)
84	if err != nil {
85		jr, err = c.validateLocal(job)
86	}
87	if err != nil {
88		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
89		return 1
90	}
91
92	if jr != nil && !jr.DriverConfigValidated {
93		c.Ui.Output(
94			c.Colorize().Color("[bold][yellow]Driver configuration not validated since connection to Nomad agent couldn't be established.[reset]\n"))
95	}
96
97	if jr != nil && jr.Error != "" {
98		c.Ui.Error(
99			c.Colorize().Color("[bold][red]Job validation errors:[reset]"))
100		c.Ui.Error(jr.Error)
101		return 1
102	}
103
104	// Print any warnings if there are any
105	if jr.Warnings != "" {
106		c.Ui.Output(
107			c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", jr.Warnings)))
108	}
109
110	// Done!
111	c.Ui.Output(
112		c.Colorize().Color("[bold][green]Job validation successful[reset]"))
113	return 0
114}
115
116// validateLocal validates without talking to a Nomad agent
117func (c *JobValidateCommand) validateLocal(aj *api.Job) (*api.JobValidateResponse, error) {
118	var out api.JobValidateResponse
119
120	job := agent.ApiJobToStructJob(aj)
121	canonicalizeWarnings := job.Canonicalize()
122
123	if vErr := job.Validate(); vErr != nil {
124		if merr, ok := vErr.(*multierror.Error); ok {
125			for _, err := range merr.Errors {
126				out.ValidationErrors = append(out.ValidationErrors, err.Error())
127			}
128			out.Error = merr.Error()
129		} else {
130			out.ValidationErrors = append(out.ValidationErrors, vErr.Error())
131			out.Error = vErr.Error()
132		}
133	}
134
135	warnings := job.Warnings()
136	out.Warnings = structs.MergeMultierrorWarnings(warnings, canonicalizeWarnings)
137	return &out, nil
138}
139