mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Add config command and config validate subcommand to nomad CLI (#9198)
This commit is contained in:
@@ -294,14 +294,14 @@ func (c *Command) readConfig() *Config {
|
||||
|
||||
config.Server.DefaultSchedulerConfig.Canonicalize()
|
||||
|
||||
if !c.isValidConfig(config, cmdConfig) {
|
||||
if !c.IsValidConfig(config, cmdConfig) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (c *Command) isValidConfig(config, cmdConfig *Config) bool {
|
||||
func (c *Command) IsValidConfig(config, cmdConfig *Config) bool {
|
||||
|
||||
// Check that the server is running in at least one mode.
|
||||
if !(config.Server.Enabled || config.Client.Enabled) {
|
||||
|
||||
@@ -398,7 +398,7 @@ func TestIsValidConfig(t *testing.T) {
|
||||
mui := cli.NewMockUi()
|
||||
cmd := &Command{Ui: mui}
|
||||
config := DefaultConfig().Merge(&tc.conf)
|
||||
result := cmd.isValidConfig(config, DefaultConfig())
|
||||
result := cmd.IsValidConfig(config, DefaultConfig())
|
||||
if tc.err == "" {
|
||||
// No error expected
|
||||
assert.True(t, result, mui.ErrorWriter.String())
|
||||
|
||||
@@ -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{
|
||||
|
||||
38
command/config.go
Normal file
38
command/config.go
Normal file
@@ -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
|
||||
}
|
||||
89
command/config_validate.go
Normal file
89
command/config_validate.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
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...>]
|
||||
|
||||
Perform validation on a set of Nomad configuration files. This is useful
|
||||
to test the Nomad configuration without starting the agent.
|
||||
|
||||
Accepts the path to either a single config file or a directory of
|
||||
config files to use for configuring the Nomad agent. This option may
|
||||
be specified multiple times. If multiple config files are used, the
|
||||
values from each will be merged together. During merging, values from
|
||||
files found later in the list are merged over values from previously
|
||||
parsed files.
|
||||
|
||||
This command cannot operate on partial configuration fragments since
|
||||
those won't pass the full agent validation. This command does not
|
||||
require an ACL token.
|
||||
|
||||
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 {
|
||||
var mErr multierror.Error
|
||||
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
|
||||
}
|
||||
|
||||
config := agent.DefaultConfig()
|
||||
|
||||
for _, path := range configPath {
|
||||
fc, err := agent.LoadConfig(path)
|
||||
if err != nil {
|
||||
multierror.Append(&mErr, fmt.Errorf(
|
||||
"Error loading configuration from %s: %s", path, err))
|
||||
continue
|
||||
}
|
||||
if fc == nil || reflect.DeepEqual(fc, &agent.Config{}) {
|
||||
c.Ui.Warn(fmt.Sprintf("No configuration loaded from %s", path))
|
||||
}
|
||||
|
||||
config = config.Merge(fc)
|
||||
}
|
||||
if err := mErr.ErrorOrNil(); err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
cmd := agent.Command{Ui: c.Ui}
|
||||
valid := cmd.IsValidConfig(config, agent.DefaultConfig())
|
||||
if !valid {
|
||||
c.Ui.Error("Configuration is invalid")
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output("Configuration is valid!")
|
||||
return 0
|
||||
}
|
||||
106
command/config_validate_test.go
Normal file
106
command/config_validate_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestConfigValidateCommand_FailWithEmptyDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
fh, err := ioutil.TempDir("", "nomad")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(fh)
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ConfigValidateCommand{Meta: Meta{Ui: ui}}
|
||||
args := []string{fh}
|
||||
|
||||
code := cmd.Run(args)
|
||||
if code != 1 {
|
||||
t.Fatalf("expected exit 1, 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(`data_dir="/"
|
||||
client {
|
||||
enabled = true
|
||||
}`), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
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_FailOnParseBadConfigFile(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(`a: b`), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ConfigValidateCommand{Meta: Meta{Ui: ui}}
|
||||
args := []string{fh}
|
||||
|
||||
code := cmd.Run(args)
|
||||
if code != 1 {
|
||||
t.Fatalf("expected exit 1, actual: %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidateCommand_FailOnValidateParsableConfigFile(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(`data_dir="../"
|
||||
client {
|
||||
enabled = true
|
||||
}`), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
cmd := &ConfigValidateCommand{Meta: Meta{Ui: ui}}
|
||||
args := []string{fh}
|
||||
|
||||
code := cmd.Run(args)
|
||||
if code != 1 {
|
||||
t.Fatalf("expected exit 1, actual: %d", code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user