Skip to content

Commit

Permalink
(feat): Layer architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed Nov 22, 2023
1 parent 2ea24ac commit cbab7f9
Show file tree
Hide file tree
Showing 43 changed files with 2,358 additions and 1,754 deletions.
87 changes: 56 additions & 31 deletions cmd/ebs-bootstrap.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,69 @@
package main

import (
"os"
"log"
"ebs-bootstrap/internal/config"
"ebs-bootstrap/internal/service"
"ebs-bootstrap/internal/utils"
"ebs-bootstrap/internal/state"
"os"

"github.com/reecetech/ebs-bootstrap/internal/action"
"github.com/reecetech/ebs-bootstrap/internal/backend"
"github.com/reecetech/ebs-bootstrap/internal/config"
"github.com/reecetech/ebs-bootstrap/internal/layer"
"github.com/reecetech/ebs-bootstrap/internal/service"
"github.com/reecetech/ebs-bootstrap/internal/utils"
)

func main() {
// Disable Timetamp
log.SetFlags(0)
e := utils.NewExecRunner()
ds := &service.LinuxDeviceService{Runner: e}
ns := &service.AwsNVMeService{}
fs := &service.UnixFileService{}
dts := &service.EbsDeviceTranslator{DeviceService: ds, NVMeService: ns}

dt, err := dts.GetTranslator()
if err != nil {
log.Fatal(err)
}
config, err := config.New(os.Args, dt, fs)
// Services
rc := utils.NewRunnerCache()
ufs := service.NewUnixFileService()
lds := service.NewLinuxDeviceService(rc)
uos := service.NewUnixOwnerService()
ans := service.NewAwsNitroNVMeService()

// Config + Flags
c, f, err := config.Parse(os.Args)
checkError(err)

// Service + Config Consumers
db := backend.NewLinuxDeviceBackend(lds)
fb := backend.NewLinuxFileBackend(ufs)
ub := backend.NewLinuxOwnerBackend(uos)
ae := action.NewActionExecutor(rc, c)

// Modify Config
bm := config.NewBatchModifier([]config.Modifier{
config.NewOverridesModifier(f),
config.NewAwsNVMeDriverModifier(ans, lds),
})
checkError(bm.Modify(c))

// Validate Config
bv := config.NewBatchValidator([]config.Validator{
config.NewDeviceValidator(lds),
config.NewFileSystemValidator(),
config.NewModeValidator(),
config.NewMountPointValidator(),
config.NewPermissionsValidator(),
config.NewOwnerValidator(uos),
})
checkError(bv.Validate(c))
// Layers
layers := layer.NewLayerExecutor([]layer.Layer{
layer.NewFormatDeviceLayer(db),
layer.NewLabelDeviceLayer(db),
layer.NewUnmountDeviceLayer(db, fb),
layer.NewCreateDirectoryLayer(db, fb),
layer.NewMountDeviceLayer(db, fb),
layer.NewChangeOwnerLayer(ub, fb),
layer.NewChangePermissionsLayer(fb),
})
checkError(layers.Execute(c, ae))
}

func checkError(err error) {
if err != nil {
log.Fatal(err)
}

for name, device := range config.Devices {
d, err := state.NewDevice(name, ds, fs)
if err != nil {
log.Fatal(err)
}
err = d.Diff(config)
if err == nil {
log.Printf("🟢 %s: No changes detected", name)
continue
}
if device.Mode == "healthcheck" {
log.Fatal(err)
}
}
}
11 changes: 0 additions & 11 deletions configs/ebs-bootstrap.yml

This file was deleted.

11 changes: 11 additions & 0 deletions configs/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defaults:
mode: healthcheck
devices:
/dev/vdb:
fs: ext4
label: lasith-rules
mountPoint: /ifmx/dev/james
mountOptions: defaults
group: ubuntu
user: ubuntu
permissions: 644
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module ebs-bootstrap
module "github.com/reecetech/ebs-bootstrap"

go 1.21

Expand Down
110 changes: 110 additions & 0 deletions internal/action/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package action

import (
"fmt"
"log"
"strings"
"time"

"github.com/reecetech/ebs-bootstrap/internal/config"
"github.com/reecetech/ebs-bootstrap/internal/utils"
)

const (
// Device operations like mounting and formatting are
// delegeated to respective C-based tools like `mount` and `mkfs`.
// From experience, we need to introduce a slight delay to ensure
// that the file-system is eventually consistent with any changes
// that were performed
DefaultDeviceActionDelay = 100 * time.Millisecond
// File changes like os.Chown and os.Chmod are performed
// natively through golang standard libraries. Since these standard
// libraries are making direct syscalls, changes are reflected almost
// immidiately on the file-system. Therefore a delay is not required
// for actions that peform file changes
DefaultFileActionDelay = 0
)

type Action interface {
Execute(rc *utils.RunnerCache) error
GetdeviceName() string
Success() string
Prompt() string
Refuse() string
IsTrusted() bool
GetDelay() time.Duration
}

type ActionExecutor struct {
runnerCache *utils.RunnerCache
config *config.Config
}

func NewActionExecutor(rc *utils.RunnerCache, c *config.Config) *ActionExecutor {
return &ActionExecutor{
runnerCache: rc,
config: c,
}
}

func (ae *ActionExecutor) ExecuteAction(action Action) error {
name := action.GetdeviceName()
mode, err := ae.config.GetMode(name)
if err != nil {
return err
}

switch mode {
case config.Prompt:
if !ae.ShouldProceed(action) {
return fmt.Errorf("🔴 Action rejected. %s", action.Refuse())
}
case config.Healtcheck:
// Special handling for trusted actions when device is under
// healthcheck mode
if action.IsTrusted() {
if ae.config.GetSkipTrustedActions() {
log.Printf("🙅 Skipped trusted action. %s", action.Refuse())
return nil
}
break
}
return fmt.Errorf("🔴%s: Healthcheck mode enabled. %s", name, action.Refuse())
}
return ae.executeAction(action)
}

func (ae *ActionExecutor) executeAction(action Action) error {
if err := action.Execute(ae.runnerCache); err != nil {
return err
}
log.Printf("⭐ %s: %s", action.GetdeviceName(), action.Success())
// Add a delay because from experience, the operating system needs some time
// to catch up to device changes performed through C tools like e2label and mkfs.ext4
time.Sleep(action.GetDelay())
return nil
}

func (ae *ActionExecutor) ExecuteActions(actions []Action) error {
for _, action := range actions {
err := ae.ExecuteAction(action)
if err != nil {
return err
}
}
return nil
}

func (ae *ActionExecutor) ShouldProceed(action Action) bool {
prompt := action.Prompt()

fmt.Printf("🟣 %s? (y/n): ", prompt)
var response string
fmt.Scanln(&response)

response = strings.ToLower(response)
if response == "y" || response == "yes" {
return true
}
return false
}
144 changes: 144 additions & 0 deletions internal/action/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package action

import (
"fmt"
"os"
"time"

"github.com/reecetech/ebs-bootstrap/internal/model"
"github.com/reecetech/ebs-bootstrap/internal/utils"
)

const (
DefaultDirectoryPermissions = os.FileMode(0755)
)

type CreateDirectoryAction struct {
deviceName string
path string
}

func NewCreateDirectoryAction(dn string, p string) *CreateDirectoryAction {
return &CreateDirectoryAction{
deviceName: dn,
path: p,
}
}

func (a *CreateDirectoryAction) Execute(rc *utils.RunnerCache) error {
return os.MkdirAll(a.path, DefaultDirectoryPermissions)
}

func (a *CreateDirectoryAction) Prompt() string {
return fmt.Sprintf("Would you like to recursively create directory %s", a.path)
}

func (a *CreateDirectoryAction) GetdeviceName() string {
return a.deviceName
}

func (a *CreateDirectoryAction) Refuse() string {
return fmt.Sprintf("Refused to create directory %s", a.path)
}

func (a *CreateDirectoryAction) Success() string {
return fmt.Sprintf("Successfully created directory %s", a.path)
}

func (a *CreateDirectoryAction) IsTrusted() bool {
return false
}

func (a *CreateDirectoryAction) GetDelay() time.Duration {
return DefaultFileActionDelay
}

type ChangeOwnerAction struct {
deviceName string
path string
uid int
gid int
}

func NewChangeOwnerAction(dn string, p string, uid int, gid int) *ChangeOwnerAction {
return &ChangeOwnerAction{
deviceName: dn,
path: p,
uid: uid,
gid: gid,
}
}

func (a *ChangeOwnerAction) Execute(rc *utils.RunnerCache) error {
return os.Chown(a.path, a.uid, a.gid)
}

func (a *ChangeOwnerAction) Prompt() string {
return fmt.Sprintf("Would you like to change ownership (%d:%d) of %s", a.uid, a.gid, a.path)
}

func (a *ChangeOwnerAction) GetdeviceName() string {
return a.deviceName
}

func (a *ChangeOwnerAction) Refuse() string {
return fmt.Sprintf("Refused to to change ownership (%d:%d) of %s", a.uid, a.gid, a.path)
}

func (a *ChangeOwnerAction) Success() string {
return fmt.Sprintf("Successfully changed ownership (%d:%d) of %s", a.uid, a.gid, a.path)
}

func (a *ChangeOwnerAction) IsTrusted() bool {
return false
}

func (a *ChangeOwnerAction) GetDelay() time.Duration {
return DefaultFileActionDelay
}

type ChangePermissionsAction struct {
deviceName string
path string
perms model.Permissions
}

func NewChangePermissions(dn string, p string, perms model.Permissions) *ChangePermissionsAction {
return &ChangePermissionsAction{
deviceName: dn,
path: p,
perms: perms,
}
}

func (a *ChangePermissionsAction) Execute(rc *utils.RunnerCache) error {
mode, err := a.perms.ToFileMode()
if err != nil {
return err
}
return os.Chmod(a.path, mode)
}

func (a *ChangePermissionsAction) Prompt() string {
return fmt.Sprintf("Would you like to change permissions of %s to %s", a.path, a.perms)
}

func (a *ChangePermissionsAction) GetdeviceName() string {
return a.deviceName
}

func (a *ChangePermissionsAction) Refuse() string {
return fmt.Sprintf("Refused to to change permissions of %s to %s", a.path, a.perms)
}

func (a *ChangePermissionsAction) Success() string {
return fmt.Sprintf("Successfully change permissions of %s to %s", a.path, a.perms)
}

func (a *ChangePermissionsAction) IsTrusted() bool {
return false
}

func (a *ChangePermissionsAction) GetDelay() time.Duration {
return DefaultFileActionDelay
}
Loading

0 comments on commit cbab7f9

Please sign in to comment.