From 504dc2e07244376434e81481da8c756c94340d17 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 24 Sep 2015 18:29:46 -0700 Subject: [PATCH] command/validate: Adding new command --- command/validate.go | 60 +++++++++++ command/validate_test.go | 102 ++++++++++++++++++ commands.go | 6 ++ .../source/docs/commands/validate.html.md.erb | 26 +++++ website/source/layouts/docs.erb | 3 + 5 files changed, 197 insertions(+) create mode 100644 command/validate.go create mode 100644 command/validate_test.go create mode 100644 website/source/docs/commands/validate.html.md.erb diff --git a/command/validate.go b/command/validate.go new file mode 100644 index 000000000..10d03eaca --- /dev/null +++ b/command/validate.go @@ -0,0 +1,60 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/hashicorp/nomad/jobspec" +) + +type ValidateCommand struct { + Meta +} + +func (c *ValidateCommand) Help() string { + helpText := ` +Usage: nomad validate [options] + + Checks if a given job file has a valid specification. This can be used to check + for any syntax errors or validation problems with a job. + +` + return strings.TrimSpace(helpText) +} + +func (c *ValidateCommand) Synopsis() string { + return "Checks if a given job specification is valid" +} + +func (c *ValidateCommand) Run(args []string) int { + flags := c.Meta.FlagSet("validate", FlagSetNone) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got exactly one node + args = flags.Args() + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + file := args[0] + + // Parse the job file + job, err := jobspec.ParseFile(file) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err)) + return 1 + } + + // Check that the job is valid + if err := job.Validate(); err != nil { + c.Ui.Error(fmt.Sprintf("Error validating job: %s", err)) + return 1 + } + + // Done! + c.Ui.Output("Job validation successful") + return 0 +} diff --git a/command/validate_test.go b/command/validate_test.go new file mode 100644 index 000000000..7e61c19b6 --- /dev/null +++ b/command/validate_test.go @@ -0,0 +1,102 @@ +package command + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestValidateCommand_Implements(t *testing.T) { + var _ cli.Command = &ValidateCommand{} +} + +func TestValidateCommand(t *testing.T) { + ui := new(cli.MockUi) + cmd := &ValidateCommand{Meta: Meta{Ui: ui}} + + fh, err := ioutil.TempFile("", "nomad") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(fh.Name()) + _, err = fh.WriteString(` +job "job1" { + datacenters = [ "dc1" ] + group "group1" { + count = 1 + task "task1" { + driver = "exec" + resources = { + cpu = 1000 + mem = 512 + } + } + } +}`) + if err != nil { + t.Fatalf("err: %s", err) + } + if code := cmd.Run([]string{fh.Name()}); code != 0 { + t.Fatalf("expect exit 0, got: %d: %s", code, ui.ErrorWriter.String()) + } +} + +func TestValidateCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &ValidateCommand{Meta: Meta{Ui: ui}} + + // Fails on misuse + if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) { + t.Fatalf("expected help output, got: %s", out) + } + ui.ErrorWriter.Reset() + + // Fails when specified file does not exist + if code := cmd.Run([]string{"/unicorns/leprechauns"}); code != 1 { + t.Fatalf("expect exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error parsing") { + t.Fatalf("expect parsing error, got: %s", out) + } + ui.ErrorWriter.Reset() + + // Fails on invalid HCL + fh1, err := ioutil.TempFile("", "nomad") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(fh1.Name()) + if _, err := fh1.WriteString("nope"); err != nil { + t.Fatalf("err: %s", err) + } + if code := cmd.Run([]string{fh1.Name()}); code != 1 { + t.Fatalf("expect exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error parsing") { + t.Fatalf("expect parsing error, got: %s", err) + } + ui.ErrorWriter.Reset() + + // Fails on invalid job spec + fh2, err := ioutil.TempFile("", "nomad") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(fh2.Name()) + if _, err := fh2.WriteString(`job "job1" {}`); err != nil { + t.Fatalf("err: %s", err) + } + if code := cmd.Run([]string{fh2.Name()}); code != 1 { + t.Fatalf("expect exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error validating") { + t.Fatalf("expect validation error, got: %s", out) + } + ui.ErrorWriter.Reset() +} diff --git a/commands.go b/commands.go index d7c74d008..46558b885 100644 --- a/commands.go +++ b/commands.go @@ -106,6 +106,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { }, nil }, + "validate": func() (cli.Command, error) { + return &command.ValidateCommand{ + Meta: meta, + }, nil + }, + "version": func() (cli.Command, error) { ver := Version rel := VersionPrerelease diff --git a/website/source/docs/commands/validate.html.md.erb b/website/source/docs/commands/validate.html.md.erb new file mode 100644 index 000000000..342c68ad9 --- /dev/null +++ b/website/source/docs/commands/validate.html.md.erb @@ -0,0 +1,26 @@ +--- +layout: "docs" +page_title: "Commands: validate" +sidebar_current: "docs-commands-validate" +description: > + The validate command is used to check a job specification for syntax errors and validation problems. +--- + +# Command: validate + +The `validate` command is used to check a [job specification](/docs/jobspec/index.html) +for any syntax errors or validation problems. + +## Usage + +``` +nomad validate +``` + +The validate command requires a single argument, specifying the path to a file +containing a valid [job specification](/docs/jobspec/index.html). This file +will be read and the job checked for any problems. + +On successful validation, exit code 0 will be returned, otherwise an exit code +of 1 indicates an error. + diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 1d7d5cf2c..74e5a5d3e 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -105,6 +105,9 @@ > stop + + > + validate > version