diff --git a/cmd/main.go b/cmd/main.go index 4d611ad..970a0bc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -21,8 +21,8 @@ func main() { lds := service.NewLinuxDeviceService(rc) uos := &service.UnixOwnerService{} - // Config - c, err := config.New(os.Args) + // Config + Flags + c, f, err := config.Parse(os.Args) checkError(err) // Service + Config Consumers @@ -31,8 +31,24 @@ func main() { ub := backend.NewLinuxOwnerBackend(uos) ae := action.NewActionExecutor(action.DefaultDelay, rc, c) + // Modify Config + bm := config.NewBatchModifier([]config.Modifier{ + config.NewOverridesModifier(f), + }) + checkError(bm.Modify(c)) + + // Validate Config + bv := config.NewBatchValidator([]config.Validator{ + config.NewFileSystemValidator(), + config.NewModeValidator(), + config.NewMountPointValidator(), + config.NewPermissionsValidator(), + config.NewOwnerValidator(uos), + }) + checkError(bv.Validate(c)) + // Layers - layers := []layer.Layer{ + layers := layer.NewLayerExecutor([]layer.Layer{ layer.NewFormatDeviceLayer(db, ae, c), layer.NewLabelDeviceLayer(db, ae, c), layer.NewUnmountDeviceLayer(db, fb, ae, c), @@ -40,13 +56,8 @@ func main() { layer.NewMountDeviceLayer(db, fb, ae, c), layer.NewChangeOwnerLayer(ub, fb, ae, c), layer.NewChangePermissionsLayer(fb, ae, c), - } - for _, layer := range layers { - checkError(layer.Refresh()) - checkError(layer.Modify()) - checkError(layer.Refresh()) - checkError(layer.Validate()) - } + }) + checkError(layers.Execute()) } func checkError(err error) { diff --git a/configs/ubuntu.yml b/configs/ubuntu.yml index a398276..5cece4e 100644 --- a/configs/ubuntu.yml +++ b/configs/ubuntu.yml @@ -1,4 +1,4 @@ -globals: +defaults: mode: healthcheck devices: /dev/vdb: diff --git a/internal/action/action.go b/internal/action/action.go index f29edcc..5bccd02 100644 --- a/internal/action/action.go +++ b/internal/action/action.go @@ -26,7 +26,6 @@ type ActionExecutor struct { Delay time.Duration RunnerCache *utils.RunnerCache Config *config.Config - MaxAttempts int } func NewActionExecutor(delay time.Duration, rc *utils.RunnerCache, c *config.Config) *ActionExecutor { @@ -34,7 +33,6 @@ func NewActionExecutor(delay time.Duration, rc *utils.RunnerCache, c *config.Con Delay: delay, RunnerCache: rc, Config: c, - MaxAttempts: 3, } } @@ -49,7 +47,7 @@ func (ae *ActionExecutor) ExecuteAction(action Action) error { return fmt.Errorf("🔴 %s: Healthcheck mode enabled. %s", name, action.Refuse()) } if mode == config.Prompt && !ae.ShouldProceed(action) { - return fmt.Errorf("🔴 %s. Rejected or exceeded the maximum attempts", action.Refuse()) + return fmt.Errorf("🔴 Action rejected. %s", action.Refuse()) } err = action.Execute(ae.RunnerCache) @@ -74,22 +72,15 @@ func (ae *ActionExecutor) ExecuteActions(actions []Action) error { } func (ae *ActionExecutor) ShouldProceed(action Action) bool { - for attempt := 1; attempt <= ae.MaxAttempts; attempt++ { - prompt := action.Prompt() + prompt := action.Prompt() - fmt.Printf("🟣 %s? (y/n): ", prompt) - var response string - fmt.Scanln(&response) + fmt.Printf("🟣 %s? (y/n): ", prompt) + var response string + fmt.Scanln(&response) - response = strings.ToLower(response) - if response == "y" || response == "yes" { - return true - } else if response == "n" || response == "no" { - return false - } - - log.Printf("🟡 Invalid response. Please enter 'y' or 'n'. Attempts left: %d\n", ae.MaxAttempts-attempt) + response = strings.ToLower(response) + if response == "y" || response == "yes" { + return true } - return false } diff --git a/internal/action/mount.go b/internal/action/mount.go index ccd0e48..3641ca8 100644 --- a/internal/action/mount.go +++ b/internal/action/mount.go @@ -41,11 +41,11 @@ func (a *MountDeviceAction) GetDeviceName() string { } func (a *MountDeviceAction) Refuse() string { - return fmt.Sprintf("Refused to mount %s to %s (%s)", a.Source, a.Target, a.Options) + return fmt.Sprintf("Refused to mount %s to %s", a.Source, a.Target) } func (a *MountDeviceAction) Success() string { - return fmt.Sprintf("Successfully mounted %s to %s (%s)", a.Source, a.Target, a.Options) + return fmt.Sprintf("Successfully mounted %s to %s", a.Source, a.Target) } type UnmountDeviceAction struct { diff --git a/internal/config/config.go b/internal/config/config.go index 627b6f5..d625610 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -53,7 +53,7 @@ type Device struct { Mode Mode `yaml:"mode"` } -type Globals struct { +type Defaults struct { Mode Mode `yaml:"mode"` } @@ -62,61 +62,36 @@ type Overrides struct { } type Config struct { - Globals Globals `yaml:"globals"` + Defaults Defaults `yaml:"defaults"` + Overrides Overrides `yaml:"overrides"` Devices map[string]Device `yaml:"devices"` - Overrides Overrides } -func New(args []string) (*Config, error) { +func Parse(args []string) (*Config, *Flag, error) { // Generate config path flag, err := parseFlags(args[0], args[1:]) if err != nil { fmt.Fprint(os.Stderr, err) - return nil, fmt.Errorf("🔴 Failed to parse provided flags") + return nil, nil, fmt.Errorf("🔴 Failed to parse provided flags") } // Create config structure - c := &Config{} + config := &Config{} // Load config file into memory file, err := os.ReadFile(flag.Config) if err != nil { - return nil, err + return nil, nil, err } // Unmarshal YAML file from memory into struct - err = yaml.UnmarshalStrict(file, c) + err = yaml.UnmarshalStrict(file, config) if err != nil { fmt.Fprintln(os.Stderr, err) - return nil, fmt.Errorf("🔴 %s: Failed to ingest malformed config", flag.Config) + return nil, nil, fmt.Errorf("🔴 %s: Failed to ingest malformed config", flag.Config) } - // Modfiy config - modifiers := []Modifier{ - NewOverridesModifier(flag), - } - for _, modifier := range modifiers { - err = modifier.Modify(c) - if err != nil { - return nil, err - } - } - - // Validate config - validators := []Validator{ - NewFileSystemValidator(), - NewModeValidator(), - NewMountPointValidator(), - NewPermissionsValidator(), - } - for _, validator := range validators { - err = validator.Validate(c) - if err != nil { - return nil, err - } - } - - return c, nil + return config, flag, nil } func parseFlags(program string, args []string) (*Flag, error) { @@ -150,8 +125,8 @@ func (c *Config) GetMode(name string) (Mode, error) { if c.Overrides.Mode != Empty { return c.Overrides.Mode, nil } - if cd.Mode == Empty && c.Globals.Mode != Empty { - return c.Globals.Mode, nil + if cd.Mode == Empty && c.Defaults.Mode != Empty { + return c.Defaults.Mode, nil } if cd.Mode != Empty { return cd.Mode, nil diff --git a/internal/config/modifier.go b/internal/config/modifier.go index 2ab8f3a..c9fb675 100644 --- a/internal/config/modifier.go +++ b/internal/config/modifier.go @@ -4,6 +4,26 @@ type Modifier interface { Modify(config *Config) error } +type BatchModifier struct { + Modifiers []Modifier +} + +func NewBatchModifier(modifiers []Modifier) *BatchModifier { + return &BatchModifier{ + Modifiers: modifiers, + } +} + +func (bv *BatchModifier) Modify(c *Config) error { + for _, modifier := range bv.Modifiers { + err := modifier.Modify(c) + if err != nil { + return err + } + } + return nil +} + type OverridesModifier struct { Flag *Flag } @@ -19,6 +39,8 @@ func (om *OverridesModifier) Modify(config *Config) error { if err != nil { return err } - config.Overrides.Mode = mode + if mode != Empty { + config.Overrides.Mode = mode + } return nil } diff --git a/internal/config/validator.go b/internal/config/validator.go index 4ed129e..bd505d5 100644 --- a/internal/config/validator.go +++ b/internal/config/validator.go @@ -5,12 +5,33 @@ import ( "path" "github.com/reecetech/vds/internal/model" + "github.com/reecetech/vds/internal/service" ) type Validator interface { Validate(c *Config) error } +type BatchValidator struct { + Validators []Validator +} + +func NewBatchValidator(validators []Validator) *BatchValidator { + return &BatchValidator{ + Validators: validators, + } +} + +func (bv *BatchValidator) Validate(c *Config) error { + for _, validator := range bv.Validators { + err := validator.Validate(c) + if err != nil { + return err + } + } + return nil +} + type FileSystemValidator struct{} func NewFileSystemValidator() *FileSystemValidator { @@ -37,7 +58,7 @@ func NewModeValidator() *ModeValidator { } func (fsv *ModeValidator) Validate(c *Config) error { - mode := string(c.Globals.Mode) + mode := string(c.Defaults.Mode) _, err := ParseMode(mode) if err != nil { return fmt.Errorf("🔴 %s is not a supported global mode", mode) @@ -95,3 +116,31 @@ func (apv *PermissionsValidator) Validate(c *Config) error { } return nil } + +type OwnerValidator struct { + OwnerService service.OwnerService +} + +func NewOwnerValidator(ons service.OwnerService) *OwnerValidator { + return &OwnerValidator{ + OwnerService: ons, + } +} + +func (ov *OwnerValidator) Validate(c *Config) error { + for _, device := range c.Devices { + if device.User != "" { + _, err := ov.OwnerService.GetUser(device.User) + if err != nil { + return err + } + } + if device.Group != "" { + _, err := ov.OwnerService.GetGroup(device.Group) + if err != nil { + return err + } + } + } + return nil +} diff --git a/internal/layer/layer.go b/internal/layer/layer.go index 08b0b7c..37c894c 100644 --- a/internal/layer/layer.go +++ b/internal/layer/layer.go @@ -1,7 +1,39 @@ package layer type Layer interface { - Refresh() (error) - Modify() (error) - Validate() (error) -} \ No newline at end of file + Refresh() error + Modify() error + Validate() error +} + +type LayerExecutor struct { + Layers []Layer +} + +func NewLayerExecutor(layers []Layer) *LayerExecutor { + return &LayerExecutor{ + Layers: layers, + } +} + +func (le *LayerExecutor) Execute() error { + for _, layer := range le.Layers { + err := layer.Refresh() + if err != nil { + return err + } + err = layer.Modify() + if err != nil { + return err + } + err = layer.Refresh() + if err != nil { + return err + } + err = layer.Validate() + if err != nil { + return err + } + } + return nil +} diff --git a/internal/layer/permissions.go b/internal/layer/permissions.go index 22b0305..b97c919 100644 --- a/internal/layer/permissions.go +++ b/internal/layer/permissions.go @@ -74,7 +74,7 @@ func (fdl *ChangePermissionsLayer) Validate() error { return fmt.Errorf("🔴 %s: Failed ownership validation checks. %s is either not a directory or does not exist", name, cd.MountPoint) } - if !d.Permissions.Equals(cd.Permissions) { + if d.Permissions.NotEquals(cd.Permissions) { return fmt.Errorf("🔴 %s: Failed permissions validation checks. %s Permissions Expected=%s, Actual=%s", name, cd.MountPoint, cd.Permissions, d.Permissions) } diff --git a/internal/model/file.go b/internal/model/file.go index 20a916d..9c1a40e 100644 --- a/internal/model/file.go +++ b/internal/model/file.go @@ -54,3 +54,7 @@ func (pa Permissions) Equals(pb Permissions) bool { } return fma == fmb } + +func (p Permissions) NotEquals(pb Permissions) bool { + return !p.Equals(pb) +} diff --git a/main b/main index 79f1076..801b115 100755 Binary files a/main and b/main differ