diff --git a/internal/pkg/inspect/inspect.go b/internal/pkg/inspect/inspect.go new file mode 100644 index 0000000..77cd2be --- /dev/null +++ b/internal/pkg/inspect/inspect.go @@ -0,0 +1,51 @@ +package inspect + +import ( + "bwizard/internal/pkg/ssh" + ssh2 "golang.org/x/crypto/ssh" +) + +type DeviceInspection struct { + Hostname string + CPU string + Ram string +} + +// Device inspects a device regarding hostname, cpu, memory and disk size +func Device(client *ssh.Client) (*DeviceInspection, error) { + session, err := client.NewSession() + if err != nil { + return nil, err + } + + defer func(session *ssh2.Session) { + err := session.Close() + if err != nil { + panic(err) + } + }(session) + + hostnameCmd := "hostname" + hostnameOutput, err := session.CombinedOutput(hostnameCmd) + if err != nil { + return nil, err + } + + cpuCmd := "lscpu | grep 'Model name' | cut -f 2 -d \":\" | awk '{$1=$1}1'" + cpuOutput, err := session.CombinedOutput(cpuCmd) + if err != nil { + return nil, err + } + + ramCmd := "cat /proc/meminfo | grep 'MemTotal' | cut -f 2 -d \":\" | awk '{$1=$1}1'" + ramOutput, err := session.CombinedOutput(ramCmd) + if err != nil { + return nil, err + } + + return &DeviceInspection{ + Hostname: string(hostnameOutput), + CPU: string(cpuOutput), + Ram: string(ramOutput), + }, nil +} diff --git a/internal/pkg/ssh/ssh.go b/internal/pkg/ssh/ssh.go new file mode 100644 index 0000000..9065814 --- /dev/null +++ b/internal/pkg/ssh/ssh.go @@ -0,0 +1,74 @@ +package ssh + +import ( + "golang.org/x/crypto/ssh" + "net" + "os" + "strconv" +) + +type Credentials struct { + Username string + PrivateKeyPath string +} + +type Client struct { + host string + port uint16 + config *ssh.ClientConfig + client *ssh.Client +} + +// NewClient returns a new SSH client +func NewClient(host string, port uint16, credentials *Credentials) (*Client, error) { + privateKey, err := os.ReadFile(credentials.PrivateKeyPath) + if err != nil { + return nil, err + } + + signer, err := ssh.ParsePrivateKey(privateKey) + if err != nil { + return nil, err + } + + config := &ssh.ClientConfig{ + User: credentials.Username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + // TODO: Only use that for development. Implement a proper host key check + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + return &Client{ + host: host, + port: port, + config: config, + }, nil +} + +// Connect starts a SSH session to the server +func (c *Client) Connect() error { + client, err := ssh.Dial("tcp", net.JoinHostPort(c.host, strconv.Itoa(int(c.port))), c.config) + if err != nil { + return err + } + + c.client = client + + return nil +} + +// Close closes the connection to the server +func (c *Client) Close() error { + if c.client == nil { + return nil + } + + return c.client.Close() +} + +// NewSession returns a new session to run commands in +func (c *Client) NewSession() (*ssh.Session, error) { + return c.client.NewSession() +} diff --git a/internal/pkg/wizard/application/svc/device/device.go b/internal/pkg/wizard/application/svc/device/device.go new file mode 100644 index 0000000..7bbfe80 --- /dev/null +++ b/internal/pkg/wizard/application/svc/device/device.go @@ -0,0 +1,53 @@ +package device + +import ( + "bwizard/internal/pkg/inspect" + "bwizard/internal/pkg/ssh" + deviceSvc "bwizard/internal/pkg/wizard/domain/svc/device" + deviceValue "bwizard/internal/pkg/wizard/domain/valueobject/device" +) + +type InspectionSvcImpl struct { +} + +var _ deviceSvc.InspectionService = &InspectionSvcImpl{} + +// Inspect gains information from a backup device such like the operating system or the agent +func (i *InspectionSvcImpl) Inspect(ips []deviceValue.IPAddress) (*deviceValue.Inspection, error) { + credentials := ssh.Credentials{ + Username: "root", + } + + var clients []*ssh.Client + for _, ip := range ips { + + client, err := ssh.NewClient(string(ip), 22, &credentials) + if err != nil { + return nil, err + } + + if err := client.Connect(); err != nil { + continue + } + + clients = append(clients, client) + + inspection, err := inspect.Device(client) + if err != nil { + return nil, err + } + + return deviceValue.NewInspectionFromDeviceInspection(inspection), nil + } + + defer func(clients []*ssh.Client) { + for _, client := range clients { + err := client.Close() + if err != nil { + panic(err) + } + } + }(clients) + + return nil, nil +} diff --git a/internal/pkg/wizard/domain/entity/device.go b/internal/pkg/wizard/domain/entity/device.go deleted file mode 100644 index eb6ef5d..0000000 --- a/internal/pkg/wizard/domain/entity/device.go +++ /dev/null @@ -1,38 +0,0 @@ -package entity - -import ( - "github.com/google/uuid" - "time" -) - -type ProtectionStatus string -type DeviceKind string - -const ( - ProtectionStatusOk ProtectionStatus = "ok" - ProtectionStatusWarning ProtectionStatus = "warning" - ProtectionStatusError ProtectionStatus = "error" - - DeviceKindServer DeviceKind = "server" - DeviceKindComputer DeviceKind = "computer" - DeviceKindMobile DeviceKind = "mobile" -) - -type OperatingSystem struct { - Name string `db:"name"` - CPU string `db:"cpu"` - Ram string `db:"ram"` - TotalHardDrive string `db:"ram"` -} - -type Device struct { - ID uuid.UUID `db:"id"` - Name string `db:"name"` - Kind DeviceKind `db:"kind"` - Protection ProtectionStatus `db:"protection"` - LastBackup *time.Time `db:"last_backup" goqu:"omitempty"` - IPs []string `db:"ips"` - Agent string `db:"agent"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} diff --git a/internal/pkg/wizard/domain/entity/device/device.go b/internal/pkg/wizard/domain/entity/device/device.go new file mode 100644 index 0000000..5d4410a --- /dev/null +++ b/internal/pkg/wizard/domain/entity/device/device.go @@ -0,0 +1,19 @@ +package device + +import ( + "bwizard/internal/pkg/wizard/domain/valueobject/device" + "github.com/google/uuid" + "time" +) + +type Device struct { + ID uuid.UUID `db:"id"` + Name string `db:"name"` + Kind device.Kind `db:"kind"` + Protection device.ProtectionStatus `db:"protection"` + LastBackup *time.Time `db:"last_backup" goqu:"omitempty"` + IPs []device.IPAddress `db:"ips"` + Agent string `db:"agent"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} diff --git a/internal/pkg/wizard/domain/repo/device.go b/internal/pkg/wizard/domain/repo/device.go deleted file mode 100644 index c7bcf19..0000000 --- a/internal/pkg/wizard/domain/repo/device.go +++ /dev/null @@ -1,12 +0,0 @@ -package repo - -import ( - "bwizard/internal/pkg/wizard/domain/entity" - "context" - "github.com/google/uuid" -) - -type DeviceRepository interface { - GetDeviceByID(ctx context.Context, id uuid.UUID) (*entity.Device, error) - SaveDevice(ctx context.Context, device *entity.Device) error -} diff --git a/internal/pkg/wizard/domain/repo/device/device.go b/internal/pkg/wizard/domain/repo/device/device.go new file mode 100644 index 0000000..e145ba7 --- /dev/null +++ b/internal/pkg/wizard/domain/repo/device/device.go @@ -0,0 +1,12 @@ +package device + +import ( + "bwizard/internal/pkg/wizard/domain/entity/device" + "context" + "github.com/google/uuid" +) + +type Repository interface { + GetDeviceByID(ctx context.Context, id uuid.UUID) (*device.Device, error) + SaveDevice(ctx context.Context, device *device.Device) error +} diff --git a/internal/pkg/wizard/domain/svc/device/device.go b/internal/pkg/wizard/domain/svc/device/device.go new file mode 100644 index 0000000..a3c4cc1 --- /dev/null +++ b/internal/pkg/wizard/domain/svc/device/device.go @@ -0,0 +1,9 @@ +package device + +import ( + "bwizard/internal/pkg/wizard/domain/valueobject/device" +) + +type InspectionService interface { + Inspect(ips []device.IPAddress) (*device.Inspection, error) +} diff --git a/internal/pkg/wizard/domain/valueobject/device/device.go b/internal/pkg/wizard/domain/valueobject/device/device.go new file mode 100644 index 0000000..f13ed54 --- /dev/null +++ b/internal/pkg/wizard/domain/valueobject/device/device.go @@ -0,0 +1,39 @@ +package device + +import "bwizard/internal/pkg/inspect" + +type ProtectionStatus string +type Kind string +type IPAddress string + +const ( + ProtectionStatusOk ProtectionStatus = "ok" + ProtectionStatusWarning ProtectionStatus = "warning" + ProtectionStatusError ProtectionStatus = "error" + + KindServer Kind = "server" + KindComputer Kind = "computer" + KindMobile Kind = "mobile" +) + +type OperatingSystem struct { + Name string `db:"name"` + CPU string `db:"cpu"` + Ram string `db:"ram"` + TotalHardDrive string `db:"ram"` +} + +type Inspection struct { + Hostname string `db:"hostname"` + CPU string `db:"cpu"` + Ram string `db:"ram"` +} + +// NewInspectionFromDeviceInspection returns a new domain inspection from inspect.DeviceInspection +func NewInspectionFromDeviceInspection(inspection *inspect.DeviceInspection) *Inspection { + return &Inspection{ + Hostname: inspection.Hostname, + CPU: inspection.CPU, + Ram: inspection.Ram, + } +} diff --git a/internal/pkg/wizard/infrastructure/mysql/repo/device.go b/internal/pkg/wizard/infrastructure/mysql/repo/device/device.go similarity index 50% rename from internal/pkg/wizard/infrastructure/mysql/repo/device.go rename to internal/pkg/wizard/infrastructure/mysql/repo/device/device.go index 6b1075e..39868c3 100644 --- a/internal/pkg/wizard/infrastructure/mysql/repo/device.go +++ b/internal/pkg/wizard/infrastructure/mysql/repo/device/device.go @@ -1,8 +1,8 @@ -package repo +package device import ( - "bwizard/internal/pkg/wizard/domain/entity" - "bwizard/internal/pkg/wizard/domain/repo" + "bwizard/internal/pkg/wizard/domain/entity/device" + device2 "bwizard/internal/pkg/wizard/domain/repo/device" "context" "database/sql" "errors" @@ -11,54 +11,54 @@ import ( ) const ( - DeviceDialect = "mysql" - DeviceTableName = "devices" + Dialect = "mysql" + TableName = "devices" ) -type DeviceRepoImpl struct { +type Impl struct { db *sql.DB } -var _ repo.DeviceRepository = &DeviceRepoImpl{} +var _ device2.Repository = &Impl{} -// NewDeviceRepository returns a new mysql device repository -func NewDeviceRepository(db *sql.DB) *DeviceRepoImpl { - return &DeviceRepoImpl{ +// NewRepository returns a new mysql device repository +func NewRepository(db *sql.DB) *Impl { + return &Impl{ db: db, } } // GetDeviceByID returns the device matching the given id from the database or nil // if the device doesn't exist -func (i *DeviceRepoImpl) GetDeviceByID(ctx context.Context, id uuid.UUID) (*entity.Device, error) { +func (i *Impl) GetDeviceByID(ctx context.Context, id uuid.UUID) (*device.Device, error) { query, _, err := goqu. - Dialect(DeviceDialect). - From(DeviceTableName). - Select(&entity.Device{}). + Dialect(Dialect). + From(TableName). + Select(&device.Device{}). ToSQL() if err != nil { return nil, err } - var device entity.Device + var deviceEntity device.Device row := i.db.QueryRowContext(ctx, query) - switch rowErr := row.Scan(&device); { + switch rowErr := row.Scan(&deviceEntity); { case errors.Is(rowErr, sql.ErrNoRows): return nil, nil case rowErr == nil: - return &device, nil + return &deviceEntity, nil default: return nil, rowErr } } // SaveDevice inserts or updates a device in the database -func (i *DeviceRepoImpl) SaveDevice(ctx context.Context, device *entity.Device) error { +func (i *Impl) SaveDevice(ctx context.Context, device *device.Device) error { insertSQL, _, _ := goqu. - Dialect(DeviceDialect). - Insert(DeviceTableName). + Dialect(Dialect). + Insert(TableName). Rows(device). OnConflict(goqu.DoUpdate("key", goqu.Record{"updated_at": goqu.L("NOW()")})). ToSQL()