mirror of
https://github.com/lorsanstand/HomeOps-Hub.git
synced 2026-06-19 17:45:17 +03:00
refactor: change structure project
This commit is contained in:
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user