refactor: change structure project

This commit is contained in:
lorsan
2026-05-03 19:17:55 +03:00
parent e289365ce8
commit c44fedb488
38 changed files with 62 additions and 59 deletions
+13
View File
@@ -0,0 +1,13 @@
package main
import (
"github.com/lorsanstand/HomeOps-Hub/agent/internal/app"
)
func main() {
start, err := app.NewApp()
if err != nil {
return
}
start.Run()
}
+72
View File
@@ -0,0 +1,72 @@
package app
import (
"context"
standartlog "log"
"github.com/docker/docker/client"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/rpc"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/service/agent_service"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/service/collector"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/service/docker_service"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/config_yaml"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/settings"
log2 "github.com/lorsanstand/HomeOps-Hub/shared/log"
"github.com/rs/zerolog"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type App struct {
log zerolog.Logger
cfg *config_yaml.AgentConfig
settings *settings.Settings
hubConn *rpc.Connection
}
func NewApp() (*App, error) {
cfg, err := config_yaml.NewConfig()
if err != nil {
standartlog.Fatalf("failed to get config: %v", err)
return nil, err
}
log := log2.NewLogger(cfg)
log = log.With().Str("component", "agent.app").Logger()
sett, err := settings.ReadSettings(cfg.SettingsPath)
if err != nil {
log.Error().Err(err).Msg("failed to get settings")
return nil, err
}
return &App{cfg: cfg, log: log, settings: sett}, nil
}
func (a *App) Run() {
ctx := context.Background()
GRPCConn, err := grpc.NewClient(a.cfg.GetGRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
a.log.Error().Err(err).Msg("failed to get agent connections")
return
}
conn := rpc.NewConnectAgent(GRPCConn, a.log)
defer conn.Close()
var DockerService collector.Docker
DockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
a.log.Warn().Err(err).Msg("failed to get docker API")
DockerService = docker_service.NewBadDocker("not_installed")
} else {
DockerService = docker_service.NewDockerService(DockerClient, a.log)
}
collect := collector.NewCollector(DockerService, a.log)
agent := agent_service.NewAgentService(collect, conn, a.settings, a.cfg, a.log)
agent.RegisterAgentConn(ctx)
}
+39
View File
@@ -0,0 +1,39 @@
package rpc
import (
"context"
pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops"
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
"github.com/rs/zerolog"
"google.golang.org/grpc"
)
type Connection struct {
hub pb.HubClient
conn *grpc.ClientConn
log zerolog.Logger
}
func NewConnectAgent(conn *grpc.ClientConn, logger zerolog.Logger) *Connection {
logger = logger.With().Str("component", "agent.rpc").Logger()
client := pb.NewHubClient(conn)
return &Connection{hub: client, conn: conn, log: logger}
}
func (c *Connection) Close() error {
c.log.Warn().Msg("connection close")
return c.conn.Close()
}
func (c *Connection) Hub() pb.HubClient {
return c.hub
}
func (c *Connection) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) {
ResponseData, err := c.Hub().RegisterAgent(ctx, new(domain.ToGRPCAgentRequest(RegisterData)))
c.log.Info().Msg("register agent")
return domain.ToDomainAgentResponse(ResponseData), err
}
@@ -0,0 +1,60 @@
package agent_service
import (
"context"
"fmt"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/config_yaml"
"github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/settings"
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
"github.com/rs/zerolog"
)
const AgentVersion = "0.0"
type Collector interface {
GatherInfoSystem() (domain.HostInfo, []domain.Capability)
}
type HubConnection interface {
RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error)
}
type AgentService struct {
collect Collector
conn HubConnection
log zerolog.Logger
cfg *config_yaml.AgentConfig
heartBeat int
settings *settings.Settings
}
func NewAgentService(
collector Collector,
conn HubConnection,
settings *settings.Settings,
cfg *config_yaml.AgentConfig,
logger zerolog.Logger,
) *AgentService {
logger = logger.With().Str("component", "cmd.service.agent_serivce").Logger()
return &AgentService{collect: collector, conn: conn, cfg: cfg, log: logger, settings: settings}
}
func (a *AgentService) RegisterAgentConn(ctx context.Context) {
info, caps := a.collect.GatherInfoSystem()
AgentID := a.settings.AgentID
AgentName := a.cfg.AppName
AgentData := domain.RegisterAgentRequest{AgentId: AgentID, AgentName: AgentName, Host: info, Capabilities: caps, AgentVersion: AgentVersion}
data, err := a.conn.RegisterAgent(ctx, AgentData)
if err != nil {
a.log.Error().Err(err).Msg("failed register cmd")
return
}
if err = a.settings.Insert(settings.Settings{AgentID: data.AgentID}); err != nil {
a.log.Warn().Err(err).Msg("failed to save cmd id")
}
fmt.Println(data)
}
@@ -0,0 +1,25 @@
package agent_service
import (
"context"
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
)
type CollectorMock struct {
host domain.HostInfo
caps []domain.Capability
}
func (c *CollectorMock) GatherInfoSystem() (domain.HostInfo, []domain.Capability) {
return c.host, c.caps
}
type ConnectionMock struct {
regAgentErr error
regResp domain.RegisterAgentResponse
}
func (c *ConnectionMock) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) {
return c.regResp, c.regAgentErr
}
@@ -0,0 +1,40 @@
package collector
import (
"os"
"runtime"
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
"github.com/rs/zerolog"
)
type Docker interface {
Capability() domain.Capability
}
type Collector struct {
log zerolog.Logger
dockerReader Docker
}
func NewCollector(docker Docker, logger zerolog.Logger) *Collector {
logger = logger.With().Str("component", "cmd.service.collector").Logger()
return &Collector{log: logger, dockerReader: docker}
}
func (c *Collector) GatherInfoSystem() (domain.HostInfo, []domain.Capability) {
var host domain.HostInfo
hostname, err := os.Hostname()
if err != nil {
c.log.Warn().Msg("failed to get hostname")
}
host.Hostname = hostname
host.Arch = runtime.GOARCH
host.System = runtime.GOOS
caps := []domain.Capability{c.dockerReader.Capability()}
return host, caps
}
@@ -0,0 +1,22 @@
package docker_service
import (
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
)
type BadDocker struct {
reason string
}
func (d *BadDocker) Capability() domain.Capability {
return domain.Capability{
Name: "docker",
Available: false,
Version: "",
Reason: d.reason,
}
}
func NewBadDocker(reason string) *BadDocker {
return &BadDocker{reason: reason}
}
@@ -0,0 +1,49 @@
package docker_service
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/lorsanstand/HomeOps-Hub/shared/domain"
"github.com/rs/zerolog"
)
type dockerAPI interface {
Ping(ctx context.Context) (types.Ping, error)
ContainerList(ctx context.Context, opts container.ListOptions) ([]container.Summary, error)
}
type DockerService struct {
dockerClient dockerAPI
log zerolog.Logger
}
func NewDockerService(api dockerAPI, logger zerolog.Logger) *DockerService {
return &DockerService{
dockerClient: api,
log: logger.With().Str("component", "cmd.serivce.docker").Logger(),
}
}
func (d *DockerService) CheckDockerDaemon(ctx context.Context) error {
_, err := d.dockerClient.Ping(ctx)
d.log.Debug().Msg("check docker")
return err
}
func (d *DockerService) ContainersList(ctx context.Context) ([]container.Summary, error) {
ContainersList, err := d.dockerClient.ContainerList(ctx, container.ListOptions{})
d.log.Debug().Msg("get container list")
return ContainersList, err
}
func (d *DockerService) Capability() domain.Capability {
return domain.Capability{
Available: true,
Version: "0",
Name: "docker",
Reason: "",
}
}
@@ -0,0 +1,135 @@
package docker_service
import (
"context"
"errors"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/rs/zerolog"
)
var testError error = errors.New("test")
type DockerMock struct {
pingErr error
containers []container.Summary
containerErr error
}
func (d DockerMock) Ping(ctx context.Context) (types.Ping, error) {
return types.Ping{}, d.pingErr
}
func (d DockerMock) ContainerList(ctx context.Context, _ container.ListOptions) ([]container.Summary, error) {
return d.containers, d.containerErr
}
func TestCheckDockerDaemon(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mock DockerMock
wantErr error
}{
{
name: "success",
mock: DockerMock{
pingErr: nil,
containers: nil,
containerErr: nil,
},
wantErr: nil,
},
{
name: "docker error",
mock: DockerMock{
pingErr: testError,
containers: nil,
containerErr: nil,
},
wantErr: testError,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
svc := NewDockerService(tt.mock, zerolog.Logger{})
err := svc.CheckDockerDaemon(context.Background())
if !errors.Is(err, tt.wantErr) {
t.Fatalf("expected error %v, got: %v", tt.wantErr, err)
}
})
}
}
func TestContainersList(t *testing.T) {
t.Parallel()
containers := []container.Summary{
{ID: "123", Image: "postgres:latest"},
{ID: "456", Image: "nginx:latest"},
}
tests := []struct {
name string
mock DockerMock
wantLen int
wantErr error
}{
{
name: "success",
mock: DockerMock{
pingErr: nil,
containers: containers,
containerErr: nil,
},
wantLen: len(containers),
wantErr: nil,
},
{
name: "docker error",
mock: DockerMock{
pingErr: nil,
containers: nil,
containerErr: testError,
},
wantLen: 0,
wantErr: testError,
},
{
name: "docker empty container",
mock: DockerMock{
pingErr: nil,
containers: nil,
containerErr: nil,
},
wantLen: 0,
wantErr: nil,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
svc := NewDockerService(tt.mock, zerolog.Logger{})
got, err := svc.ContainersList(context.Background())
if !errors.Is(err, tt.wantErr) {
t.Fatalf("expected error %v, got: %v", tt.wantErr, err)
}
if tt.wantLen != len(got) {
t.Fatalf("expected %d containers, got: %d", tt.wantLen, len(got))
}
})
}
}
@@ -0,0 +1,50 @@
package config_yaml
import (
"fmt"
"os"
"github.com/rs/zerolog"
"gopkg.in/yaml.v3"
)
type AgentConfig struct {
AppName string `yaml:"app_name"`
HubConnect struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"hub"`
LogLevel string `yaml:"log_level"`
SettingsPath string `yaml:"settings_path"`
}
func NewConfig() (*AgentConfig, error) {
yamlFile, err := os.ReadFile("agent.dev.yaml")
if err != nil {
return nil, fmt.Errorf("failed open file: %v", err)
}
var cfg AgentConfig
if err = yaml.Unmarshal(yamlFile, &cfg); err != nil {
return nil, fmt.Errorf("failed read yaml: %v", err)
}
return &cfg, nil
}
func (c *AgentConfig) GetLogLevel() zerolog.Level {
level, err := zerolog.ParseLevel(c.LogLevel)
if err != nil {
return zerolog.InfoLevel
}
return level
}
func (c *AgentConfig) GetMode() string {
return "DEV"
}
func (c *AgentConfig) GetGRPCAddress() string {
return fmt.Sprintf("%v:%v", c.HubConnect.Host, c.HubConnect.Port)
}
@@ -0,0 +1,41 @@
package config_yaml
import (
"testing"
"github.com/rs/zerolog"
)
func TestAgentConfig_GetLogLevel(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg AgentConfig
wantLogLevel zerolog.Level
}{
{
name: "success",
cfg: AgentConfig{LogLevel: "DEBUG"},
wantLogLevel: zerolog.DebugLevel,
},
{
name: "failed parse",
cfg: AgentConfig{LogLevel: "TEST"},
wantLogLevel: zerolog.InfoLevel,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
logLevel := tt.cfg.GetLogLevel()
if logLevel != tt.wantLogLevel {
t.Fatalf("expected %v, got: %v", tt.wantLogLevel, logLevel)
}
})
}
}
+66
View File
@@ -0,0 +1,66 @@
package settings
import (
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
)
type Settings struct {
AgentID string `json:"agent_id"`
path string
}
func ReadSettings(path string) (*Settings, error) {
if path == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
path = filepath.Join(homeDir, ".config", "homeops")
}
err := os.Mkdir(path, 0755)
if err != nil {
if !errors.Is(err, os.ErrExist) {
return nil, err
}
err = nil
}
settingsPath := filepath.Join(path, "settings.json")
var settings Settings
file, err := os.Open(settingsPath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
} else {
defer file.Close()
err = json.NewDecoder(file).Decode(&settings)
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
}
settings.path = settingsPath
return &settings, nil
}
func (s *Settings) Insert(sett Settings) error {
file, err := os.OpenFile(s.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer file.Close()
if err = json.NewEncoder(file).Encode(sett); err != nil {
return err
}
return nil
}