Skip to content

Commit

Permalink
Add config command and config validate subcommand to nomad CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
th0m committed Dec 13, 2021
1 parent 35c22bc commit 0993c5a
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 0 deletions.
10 changes: 10 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"config": func() (cli.Command, error) {
return &ConfigCommand{
Meta: meta,
}, nil
},
"config validate": func() (cli.Command, error) {
return &ConfigValidateCommand{
Meta: meta,
}, nil
},
// operator debug was released in 0.12 as debug. This top-level alias preserves compatibility
"debug": func() (cli.Command, error) {
return &OperatorDebugCommand{
Expand Down
38 changes: 38 additions & 0 deletions command/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package command

import (
"strings"

"github.com/mitchellh/cli"
)

type ConfigCommand struct {
Meta
}

func (f *ConfigCommand) Help() string {
helpText := `
Usage: nomad config <subcommand> [options] [args]
This command groups subcommands for interacting with configurations.
Users can validate configurations for the Nomad agent.
Validate configuration:
$ nomad config validate <config_path> [<config_path>...]
Please see the individual subcommand help for detailed usage information.
`

return strings.TrimSpace(helpText)
}

func (f *ConfigCommand) Synopsis() string {
return "Interact with configurations"
}

func (f *ConfigCommand) Name() string { return "config" }

func (f *ConfigCommand) Run(args []string) int {
return cli.RunResultHelp
}
73 changes: 73 additions & 0 deletions command/config_validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package command

import (
"fmt"
"strings"

agent "github.com/hashicorp/nomad/command/agent"
)

type ConfigValidateCommand struct {
Meta
}

func (c *ConfigValidateCommand) Help() string {
helpText := `
Usage: nomad config validate <config_path> [<config_path...>]
Performs a thorough sanity test on Nomad configuration files. For each file
or directory given, the validate command will attempt to parse the contents
just as the "nomad agent" command would, and catch any errors.
This is useful to do a test of the configuration only, without actually
starting the agent. This performs all of the validation the agent would, so
this should be given the complete set of configuration files that are going
to be loaded by the agent. This command cannot operate on partial
configuration fragments since those won't pass the full agent validation.
Returns 0 if the configuration is valid, or 1 if there are problems.
`

return strings.TrimSpace(helpText)
}

func (c *ConfigValidateCommand) Synopsis() string {
return "Validate config files/directories"
}

func (c *ConfigValidateCommand) Name() string { return "config validate" }

func (c *ConfigValidateCommand) Run(args []string) int {
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
if err := flags.Parse(args); err != nil {
c.Ui.Error(err.Error())
return 1
}

configPath := flags.Args()
if len(configPath) < 1 {
c.Ui.Error("Must specify at least one config file or directory")
return 1
}

var config *agent.Config

for _, path := range configPath {
current, err := agent.LoadConfig(path)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error loading configuration from %s: %s", path, err))
return 1
}

if config == nil {
config = current
} else {
config = config.Merge(current)
}
}

c.Ui.Output("Configuration is valid!")
return 0
}
76 changes: 76 additions & 0 deletions command/config_validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package command

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/mitchellh/cli"
)

func TestConfigValidateCommand_SucceedWithEmptyDir(t *testing.T) {
t.Parallel()
fh, err := ioutil.TempDir("", "nomad")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(fh)

ui := new(cli.MockUi)
cmd := &ConfigValidateCommand{Meta: Meta{Ui: ui}}
args := []string{fh}

code := cmd.Run(args)
if code != 0 {
t.Fatalf("expected exit 0, actual: %d", code)
}
}

func TestConfigValidateCommand_SucceedWithMinimalConfigFile(t *testing.T) {
t.Parallel()
fh, err := ioutil.TempDir("", "nomad")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(fh)

fp := filepath.Join(fh, "config.hcl")
err = ioutil.WriteFile(fp, []byte(`datacenter = "abc01"`), 0644)
if err != nil {
t.Fatalf("err: %s", err)
}

ui := new(cli.MockUi)