feat: add config validation warnings
Closes #3412. Refs #3413. Signed-off-by: Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
This commit is contained in:
parent
eee7ad13aa
commit
29da22d063
@ -94,7 +94,11 @@ message ApplyConfigurationRequest {
|
||||
}
|
||||
|
||||
// ApplyConfigurationResponse describes the response to a configuration request.
|
||||
message ApplyConfiguration { common.Metadata metadata = 1; }
|
||||
message ApplyConfiguration {
|
||||
common.Metadata metadata = 1;
|
||||
// Configuration validation warnings.
|
||||
repeated string warnings = 2;
|
||||
}
|
||||
message ApplyConfigurationResponse { repeated ApplyConfiguration messages = 1; }
|
||||
|
||||
// rpc reboot
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
var (
|
||||
validateConfigArg string
|
||||
validateModeArg string
|
||||
validateStrictArg bool
|
||||
)
|
||||
|
||||
// validateCmd reads in a userData file and attempts to parse it.
|
||||
@ -36,7 +37,17 @@ var validateCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cfg.Validate(mode, config.WithLocal()); err != nil {
|
||||
|
||||
opts := []config.ValidationOption{config.WithLocal()}
|
||||
if validateStrictArg {
|
||||
opts = append(opts, config.WithStrict())
|
||||
}
|
||||
|
||||
warnings, err := cfg.Validate(mode, opts...)
|
||||
for _, w := range warnings {
|
||||
cli.Warning("%s", w)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -56,5 +67,6 @@ func init() {
|
||||
fmt.Sprintf("the mode to validate the config for (valid values are %s, %s, and %s)", runtime.ModeMetal.String(), runtime.ModeCloud.String(), runtime.ModeContainer.String()),
|
||||
)
|
||||
cli.Should(validateCmd.MarkFlagRequired("mode"))
|
||||
validateCmd.Flags().BoolVarP(&validateStrictArg, "strict", "", false, "treat validation warnings as errors")
|
||||
addCommand(validateCmd)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/talos-systems/crypto/x509"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/tui/installer"
|
||||
"github.com/talos-systems/talos/pkg/cli"
|
||||
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client"
|
||||
)
|
||||
@ -148,11 +149,17 @@ var applyConfigCmd = &cobra.Command{
|
||||
return install.Run(conn)
|
||||
}
|
||||
|
||||
if _, err := c.ApplyConfiguration(ctx, &machineapi.ApplyConfigurationRequest{
|
||||
resp, err := c.ApplyConfiguration(ctx, &machineapi.ApplyConfigurationRequest{
|
||||
Data: cfgBytes,
|
||||
OnReboot: applyConfigCmdFlags.onReboot,
|
||||
Immediate: applyConfigCmdFlags.immediate,
|
||||
}); err != nil {
|
||||
})
|
||||
for _, m := range resp.GetMessages() {
|
||||
for _, w := range m.GetWarnings() {
|
||||
cli.Warning("%s", w)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying new configuration: %s", err)
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"k8s.io/kubectl/pkg/cmd/util/editor/crlf"
|
||||
|
||||
"github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers"
|
||||
"github.com/talos-systems/talos/pkg/cli"
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client"
|
||||
"github.com/talos-systems/talos/pkg/resources/config"
|
||||
@ -130,7 +131,7 @@ or 'notepad' for Windows.`,
|
||||
break
|
||||
}
|
||||
|
||||
_, err = c.ApplyConfiguration(parentCtx, &machine.ApplyConfigurationRequest{
|
||||
resp, err := c.ApplyConfiguration(parentCtx, &machine.ApplyConfigurationRequest{
|
||||
Data: edited,
|
||||
Immediate: editCmdFlags.immediate,
|
||||
OnReboot: editCmdFlags.onReboot,
|
||||
@ -140,6 +141,11 @@ or 'notepad' for Windows.`,
|
||||
|
||||
continue
|
||||
}
|
||||
for _, m := range resp.GetMessages() {
|
||||
for _, w := range m.GetWarnings() {
|
||||
cli.Warning("%s", w)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/talos-systems/talos/cmd/talosctl/pkg/talos/helpers"
|
||||
"github.com/talos-systems/talos/pkg/cli"
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configpatcher"
|
||||
@ -85,7 +86,7 @@ var patchCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.ApplyConfiguration(ctx, &machine.ApplyConfigurationRequest{
|
||||
resp, err := c.ApplyConfiguration(ctx, &machine.ApplyConfigurationRequest{
|
||||
Data: patched,
|
||||
Immediate: patchCmdFlags.immediate,
|
||||
OnReboot: patchCmdFlags.onReboot,
|
||||
@ -102,6 +103,12 @@ var patchCmd = &cobra.Command{
|
||||
|
||||
fmt.Printf("patched %s at the node %s\n", args[0], msg.Metadata.GetHostname())
|
||||
|
||||
for _, m := range resp.GetMessages() {
|
||||
for _, w := range m.GetWarnings() {
|
||||
cli.Warning("%s", w)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func (r *Runtime) ValidateConfig(b []byte) (config.Provider, error) {
|
||||
return nil, fmt.Errorf("failed to parse config: %w", err)
|
||||
}
|
||||
|
||||
if err := cfg.Validate(r.State().Platform().Mode()); err != nil {
|
||||
if _, err := cfg.Validate(r.State().Platform().Mode()); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate config: %w", err)
|
||||
}
|
||||
|
||||
|
@ -539,7 +539,12 @@ func receiveConfigViaMaintenanceService(ctx context.Context, logger *log.Logger,
|
||||
return nil, fmt.Errorf("failed to create config provider: %w", err)
|
||||
}
|
||||
|
||||
if err = provider.Validate(r.State().Platform().Mode()); err != nil {
|
||||
warnings, err := provider.Validate(r.State().Platform().Mode())
|
||||
for _, w := range warnings {
|
||||
logger.Printf("WARNING:\n%s", w)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate config: %w", err)
|
||||
}
|
||||
|
||||
@ -554,7 +559,12 @@ func receiveConfigViaMaintenanceService(ctx context.Context, logger *log.Logger,
|
||||
// ValidateConfig validates the config.
|
||||
func ValidateConfig(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {
|
||||
return r.Config().Validate(r.State().Platform().Mode())
|
||||
warnings, err := r.Config().Validate(r.State().Platform().Mode())
|
||||
for _, w := range warnings {
|
||||
logger.Printf("WARNING:\n%s", w)
|
||||
}
|
||||
|
||||
return err
|
||||
}, "validateConfig"
|
||||
}
|
||||
|
||||
|
@ -61,13 +61,16 @@ func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfig
|
||||
return nil, fmt.Errorf("failed to parse config: %w", err)
|
||||
}
|
||||
|
||||
if err = cfgProvider.Validate(s.runtime.State().Platform().Mode()); err != nil {
|
||||
warnings, err := cfgProvider.Validate(s.runtime.State().Platform().Mode())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("configuration validation failed: %w", err)
|
||||
}
|
||||
|
||||
reply = &machine.ApplyConfigurationResponse{
|
||||
Messages: []*machine.ApplyConfiguration{
|
||||
{},
|
||||
{
|
||||
Warnings: warnings,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ func (suite *ValidateSuite) TestValidate() {
|
||||
mode := mode
|
||||
|
||||
suite.Run(fmt.Sprintf("%s-%s", configFile, mode), func() {
|
||||
suite.RunCLI([]string{"validate", "-m", mode, "-c", configFile})
|
||||
suite.RunCLI([]string{"validate", "-m", mode, "-c", configFile, "--strict"})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,8 @@ type Provider interface {
|
||||
Persist() bool
|
||||
Machine() MachineConfig
|
||||
Cluster() ClusterConfig
|
||||
Validate(RuntimeMode, ...ValidationOption) error
|
||||
// Validate checks configuration and returns warnings and fatal errors (as multierror).
|
||||
Validate(RuntimeMode, ...ValidationOption) ([]string, error)
|
||||
ApplyDynamicConfig(context.Context, DynamicConfigProvider) error
|
||||
String(encoderOptions ...encoder.Option) (string, error)
|
||||
Bytes(encoderOptions ...encoder.Option) ([]byte, error)
|
||||
|
@ -218,6 +218,7 @@ func (m *MachineConfig) Files() ([]config.File, error) {
|
||||
}
|
||||
|
||||
// Type implements the config.Provider interface.
|
||||
// TODO use machine.ParseType? https://github.com/talos-systems/talos/issues/3413
|
||||
func (m *MachineConfig) Type() machine.Type {
|
||||
switch m.MachineType {
|
||||
case "init":
|
||||
|
@ -55,17 +55,20 @@ var (
|
||||
// NetworkDeviceCheck defines the function type for checks.
|
||||
type NetworkDeviceCheck func(*Device) error
|
||||
|
||||
// Validate implements the Configurator interface.
|
||||
// Validate implements the config.Provider interface.
|
||||
//nolint:gocyclo,cyclop
|
||||
func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationOption) error {
|
||||
var result *multierror.Error
|
||||
func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationOption) ([]string, error) {
|
||||
var (
|
||||
warnings []string
|
||||
result *multierror.Error
|
||||
)
|
||||
|
||||
opts := config.NewValidationOptions(options...)
|
||||
|
||||
if c.MachineConfig == nil {
|
||||
result = multierror.Append(result, errors.New("machine instructions are required"))
|
||||
|
||||
return result.ErrorOrNil()
|
||||
return nil, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
if err := c.ClusterConfig.Validate(); err != nil {
|
||||
@ -98,6 +101,12 @@ func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationO
|
||||
}
|
||||
}
|
||||
|
||||
// TODO rework machine type validation https://github.com/talos-systems/talos/issues/3413
|
||||
|
||||
if c.MachineConfig.MachineType == "" {
|
||||
warnings = append(warnings, `machine type is empty`)
|
||||
}
|
||||
|
||||
if c.Machine().Type() == machine.TypeInit || c.Machine().Type() == machine.TypeControlPlane {
|
||||
switch c.Cluster().Network().CNI().Name() {
|
||||
case constants.CustomCNI:
|
||||
@ -157,7 +166,15 @@ func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationO
|
||||
}
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
if opts.Strict {
|
||||
for _, w := range warnings {
|
||||
result = multierror.Append(result, fmt.Errorf("warning: %s", w))
|
||||
}
|
||||
|
||||
warnings = nil
|
||||
}
|
||||
|
||||
return warnings, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Validate validates the config.
|
||||
|
@ -35,10 +35,12 @@ func TestValidate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
config *v1alpha1.Config
|
||||
requiresInstall bool
|
||||
expectedError string
|
||||
name string
|
||||
config *v1alpha1.Config
|
||||
requiresInstall bool
|
||||
strict bool
|
||||
expectedWarnings []string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "NoMachine",
|
||||
@ -48,7 +50,7 @@ func TestValidate(t *testing.T) {
|
||||
expectedError: "1 error occurred:\n\t* machine instructions are required\n\n",
|
||||
},
|
||||
{
|
||||
name: "NoMachineInstall",
|
||||
name: "NoMachineType",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
@ -60,12 +62,49 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedWarnings: []string{
|
||||
`machine type is empty`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NoMachineTypeStrict",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
endpointURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
strict: true,
|
||||
expectedError: "1 error occurred:\n\t* warning: machine type is empty\n\n",
|
||||
},
|
||||
{
|
||||
name: "NoMachineInstall",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
endpointURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NoMachineInstallRequired",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -82,6 +121,7 @@ func TestValidate(t *testing.T) {
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
MachineInstall: &v1alpha1.InstallConfig{
|
||||
InstallDisk: "/dev/vda",
|
||||
},
|
||||
@ -101,7 +141,9 @@ func TestValidate(t *testing.T) {
|
||||
name: "ExternalCloudProviderEnabled",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -122,7 +164,9 @@ func TestValidate(t *testing.T) {
|
||||
name: "ExternalCloudProviderEnabledNoManifests",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -139,7 +183,9 @@ func TestValidate(t *testing.T) {
|
||||
name: "ExternalCloudProviderDisabled",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -154,7 +200,9 @@ func TestValidate(t *testing.T) {
|
||||
name: "ExternalCloudProviderExtraManifests",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -175,7 +223,9 @@ func TestValidate(t *testing.T) {
|
||||
name: "ExternalCloudProviderInvalidManifests",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "join",
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
@ -198,12 +248,19 @@ func TestValidate(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := test.config.Validate(runtimeMode{test.requiresInstall}, config.WithLocal())
|
||||
opts := []config.ValidationOption{config.WithLocal()}
|
||||
if test.strict {
|
||||
opts = append(opts, config.WithStrict())
|
||||
}
|
||||
|
||||
warnings, errrors := test.config.Validate(runtimeMode{test.requiresInstall}, opts...)
|
||||
|
||||
assert.Equal(t, test.expectedWarnings, warnings)
|
||||
|
||||
if test.expectedError == "" {
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, errrors)
|
||||
} else {
|
||||
assert.EqualError(t, err, test.expectedError)
|
||||
assert.EqualError(t, errrors, test.expectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ package config
|
||||
type ValidationOptions struct {
|
||||
// Local should disable part of the validation flow which won't work on the host machine.
|
||||
Local bool
|
||||
// Strict mode returns warnings as errors.
|
||||
Strict bool
|
||||
}
|
||||
|
||||
// ValidationOption represents an additional validation parameter for the config Validate method.
|
||||
@ -23,9 +25,16 @@ func NewValidationOptions(options ...ValidationOption) *ValidationOptions {
|
||||
return opts
|
||||
}
|
||||
|
||||
// WithLocal enable local flag.
|
||||
// WithLocal enables local flag.
|
||||
func WithLocal() ValidationOption {
|
||||
return func(opts *ValidationOptions) {
|
||||
opts.Local = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithStrict enables strict flag.
|
||||
func WithStrict() ValidationOption {
|
||||
return func(opts *ValidationOptions) {
|
||||
opts.Strict = true
|
||||
}
|
||||
}
|
||||
|
@ -610,6 +610,7 @@ ApplyConfigurationResponse describes the response to a configuration request.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| metadata | [common.Metadata](#common.Metadata) | | |
|
||||
| warnings | [string](#string) | repeated | Configuration validation warnings. |
|
||||
|
||||
|
||||
|
||||
|
@ -2019,6 +2019,7 @@ talosctl validate [flags]
|
||||
-c, --config string the path of the config file
|
||||
-h, --help help for validate
|
||||
-m, --mode string the mode to validate the config for (valid values are metal, cloud, and container)
|
||||
--strict treat validation warnings as errors
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
Loading…
x
Reference in New Issue
Block a user