diff --git a/.gitignore b/.gitignore index d19b928..0adaa22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env -agent.dev.yaml \ No newline at end of file +config.yaml +.idea \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2cfb250 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,47 @@ +version: "2" + +run: + # Сколько времени давать линтеру на проверку (для больших проектов лучше ставить 5m) + timeout: 3m + # Проверять ли тесты + tests: true + +linters: + # Отключаем все по умолчанию, чтобы включить только нужные нам + disable-all: true + enable: + # --- ОБЯЗАТЕЛЬНЫЕ (База) --- + - errcheck # Проверяет, не забыл ли ты обработать ошибку (if err != nil) + - govet # Официальный инструмент Go, ищет подозрительные конструкции + - ineffassign # Находит переменные, которым присвоили значение, но не использовали + - staticcheck # Огромный набор проверок на логические ошибки + - unused # Ищет неиспользуемый код (функции, переменные, типы) + + # --- ОБУЧАЮЩИЕ (Помогут новичку) --- + - revive # Замена старому golint: следит за именованием и комментариями + - errname # Проверяет, что ошибки названы по стандарту (например, ErrSentinel) + - goconst # Находит строки, которые часто повторяются (подскажет сделать константу) + - makezero # Следит, чтобы ты не делал лишних аллокаций в slice + + settings: + revive: + # Настраиваем правила + rules: + # Отключаем обязательные комментарии для экспортируемых сущностей + - name: exported + disabled: true + # Отключаем требование комментариев к пакетам + - name: package-comments + disabled: true + # Оставляем полезное: проверка именования (var_name -> varName) + - name: var-naming + severity: warning + +issues: + # Не ограничивай количество ошибок, пока учишься + max-issues-per-linter: 0 + max-same-issues: 0 + + # Исключаем некоторые папки (например, сгенерированный код) + exclude-dirs: + - vendor \ No newline at end of file diff --git a/.idea/golinter.xml b/.idea/golinter.xml new file mode 100644 index 0000000..1ccf3ec --- /dev/null +++ b/.idea/golinter.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/workspace.sync-conflict-20260504-202326-XNSB2YU.xml b/.idea/workspace.sync-conflict-20260504-202326-XNSB2YU.xml new file mode 100644 index 0000000..ef52743 --- /dev/null +++ b/.idea/workspace.sync-conflict-20260504-202326-XNSB2YU.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + { + "associatedIndex": 6 +} + + + + + + + + + + + + + + + + + + + 1775479991162 + + + + + + \ No newline at end of file diff --git a/cmd/agent/main.go b/agent/cmd/main.go similarity index 53% rename from cmd/agent/main.go rename to agent/cmd/main.go index 67febdd..9fcdbb1 100644 --- a/cmd/agent/main.go +++ b/agent/cmd/main.go @@ -1,11 +1,13 @@ package main -import "github.com/lorsanstand/HomeOps-Hub/internal/agent/app" +import ( + "github.com/lorsanstand/HomeOps-Hub/agent/internal/app" +) func main() { start, err := app.NewApp() if err != nil { - return + panic(err) } start.Run() } diff --git a/internal/agent/app/app.go b/agent/internal/app/app.go similarity index 52% rename from internal/agent/app/app.go rename to agent/internal/app/app.go index a588b6d..d7b5f8e 100644 --- a/internal/agent/app/app.go +++ b/agent/internal/app/app.go @@ -5,13 +5,13 @@ import ( standartlog "log" "github.com/docker/docker/client" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/rpc" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/agent_service" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/collector" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/service/docker_service" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/settings" - log2 "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" + "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" + "github.com/lorsanstand/HomeOps-Hub/shared/log" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -25,23 +25,23 @@ type App struct { } 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() + logger := log.NewLogger(cfg) + logger = logger.With().Str("component", "internal.app").Logger() + logger = logger.With().Str("name", cfg.AppName).Logger() sett, err := settings.ReadSettings(cfg.SettingsPath) if err != nil { - log.Error().Err(err).Msg("failed to get settings") + logger.Error().Err(err).Msg("failed to get settings") return nil, err } - return &App{cfg: cfg, log: log, settings: sett}, nil + return &App{cfg: cfg, log: logger, settings: sett}, nil } func (a *App) Run() { @@ -49,12 +49,17 @@ func (a *App) Run() { GRPCConn, err := grpc.NewClient(a.cfg.GetGRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - a.log.Error().Err(err).Msg("failed to get hub connections") + a.log.Error().Err(err).Msg("failed to connection hub") return } + a.log.Info().Msg("connection to the hub successful") - conn := rpc.NewConnectAgent(GRPCConn, a.log) - defer conn.Close() + conn := rpc.NewConnectAgent(GRPCConn) + defer func() { + if err := conn.Close(); err != nil { + a.log.Warn().Err(err).Msg("failed to close rpc connection") + } + }() var DockerService collector.Docker @@ -63,11 +68,15 @@ func (a *App) Run() { a.log.Warn().Err(err).Msg("failed to get docker API") DockerService = docker_service.NewBadDocker("not_installed") } else { + a.log.Info().Msg("successfully to get docker API") 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) + if err := agent.RegisterAgentConn(ctx); err != nil { + a.log.Error().Err(err).Msg("failed to agent registration") + } + a.log.Info().Msg("agent registration complete") } diff --git a/internal/agent/rpc/client.go b/agent/internal/rpc/client.go similarity index 62% rename from internal/agent/rpc/client.go rename to agent/internal/rpc/client.go index a945085..23f9ea3 100644 --- a/internal/agent/rpc/client.go +++ b/agent/internal/rpc/client.go @@ -4,27 +4,21 @@ import ( "context" pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" - "github.com/rs/zerolog" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" "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() - +func NewConnectAgent(conn *grpc.ClientConn) *Connection { client := pb.NewHubClient(conn) - - return &Connection{hub: client, conn: conn, log: logger} + return &Connection{hub: client, conn: conn} } func (c *Connection) Close() error { - c.log.Warn().Msg("connection close") return c.conn.Close() } @@ -34,6 +28,5 @@ func (c *Connection) Hub() pb.HubClient { 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 } diff --git a/agent/internal/service/agent_service/agent.go b/agent/internal/service/agent_service/agent.go new file mode 100644 index 0000000..6eb18b0 --- /dev/null +++ b/agent/internal/service/agent_service/agent.go @@ -0,0 +1,71 @@ +package agent_service + +import ( + "context" + "fmt" + + "github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/config_yaml" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" + "github.com/rs/zerolog" +) + +type Collector interface { + GatherInfoSystem() (domain.HostInfo, []domain.Capability) +} + +type Settings interface { + InsertAgentID(agentID string) error + GetAgentID() string +} + +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 +} + +func NewAgentService( + collector Collector, + conn HubConnection, + settings Settings, + cfg *config_yaml.AgentConfig, + logger zerolog.Logger, +) *AgentService { + logger = logger.With().Str("component", "internal.service.agent_serivce").Logger() + + return &AgentService{collect: collector, conn: conn, cfg: cfg, log: logger, settings: settings} +} + +func (a *AgentService) RegisterAgentConn(ctx context.Context) error { + a.log.Debug().Msg("getting info by system") + info, caps := a.collect.GatherInfoSystem() + a.log.Debug().Msg("create request data for register agent") + AgentID := a.settings.GetAgentID() + AgentName := a.cfg.AppName + AgentData := domain.RegisterAgentRequest{ + AgentID: AgentID, + AgentName: AgentName, + Host: info, + Capabilities: caps, + AgentVersion: a.cfg.GetAgentVersion(), + } + + data, err := a.conn.RegisterAgent(ctx, AgentData) + if err != nil { + return fmt.Errorf("register agent: %w", err) + } + + if err = a.settings.InsertAgentID(data.AgentID); err != nil { + return fmt.Errorf("save agent ID: %w", err) + } + a.log.Info().Str("agentID", data.AgentID).Msg("agent registration end") + + return nil +} diff --git a/agent/internal/service/agent_service/agent_test.go b/agent/internal/service/agent_service/agent_test.go new file mode 100644 index 0000000..a229c03 --- /dev/null +++ b/agent/internal/service/agent_service/agent_test.go @@ -0,0 +1,109 @@ +package agent_service + +import ( + "context" + "errors" + "testing" + + "github.com/lorsanstand/HomeOps-Hub/agent/internal/utils/config_yaml" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" + "github.com/rs/zerolog" +) + +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 + regData domain.RegisterAgentRequest +} + +func (c *ConnectionMock) RegisterAgent(ctx context.Context, RegisterData domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { + c.regData = RegisterData + return c.regResp, c.regAgentErr +} + +type SettingsMock struct { + insertErr error + agentID string + countUse int +} + +func (s *SettingsMock) InsertAgentID(agentID string) error { + s.agentID = agentID + s.countUse++ + return s.insertErr +} + +func (s *SettingsMock) GetAgentID() string { + return s.agentID +} + +func TestAgentService_RegisterAgentConn(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + wantErr error + insertAgentIDUse int + settings SettingsMock + collector CollectorMock + conn ConnectionMock + cfg config_yaml.AgentConfig + }{ + { + name: "success", + wantErr: nil, + insertAgentIDUse: 1, + settings: SettingsMock{agentID: "", insertErr: nil}, + collector: CollectorMock{ + host: domain.HostInfo{System: "Linux", Hostname: "test", Arch: "x64"}, + caps: []domain.Capability{ + {Available: true, Version: "0", Name: "testCaps", Reason: ""}, + }, + }, + conn: ConnectionMock{regAgentErr: nil, regResp: domain.RegisterAgentResponse{AgentID: "123", Heartbeat: 4}}, + cfg: config_yaml.AgentConfig{AppName: "test"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + + svc := NewAgentService(&tt.collector, + &tt.conn, + &tt.settings, + &tt.cfg, + zerolog.New(nil), + ) + + err := svc.RegisterAgentConn(ctx) + if !errors.Is(err, tt.wantErr) { + t.Fatalf("expected error %v, got: %v", tt.wantErr, err) + } + + if tt.insertAgentIDUse != tt.settings.countUse { + t.Errorf("expected count insert agent id %v, got: %v", tt.insertAgentIDUse, tt.settings.countUse) + } + + if tt.settings.agentID != tt.conn.regResp.AgentID { + t.Errorf("expected insert agent id %v, got: %v", tt.conn.regResp.AgentID, tt.settings.agentID) + } + + if tt.cfg.AppName != tt.conn.regData.AgentName { + t.Fatalf("expected agent name %v, got: %v", tt.cfg.AppName, tt.conn.regData.AgentName) + } + + }) + } +} diff --git a/internal/agent/service/collector/collector.go b/agent/internal/service/collector/collector.go similarity index 84% rename from internal/agent/service/collector/collector.go rename to agent/internal/service/collector/collector.go index 93cea10..e0351ed 100644 --- a/internal/agent/service/collector/collector.go +++ b/agent/internal/service/collector/collector.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" "github.com/rs/zerolog" ) @@ -18,7 +18,7 @@ type Collector struct { } func NewCollector(docker Docker, logger zerolog.Logger) *Collector { - logger = logger.With().Str("component", "agent.service.collector").Logger() + logger = logger.With().Str("component", "internal.service.collector").Logger() return &Collector{log: logger, dockerReader: docker} } diff --git a/internal/agent/service/docker_service/bad.go b/agent/internal/service/docker_service/bad.go similarity index 85% rename from internal/agent/service/docker_service/bad.go rename to agent/internal/service/docker_service/bad.go index 6c48587..ed02d48 100644 --- a/internal/agent/service/docker_service/bad.go +++ b/agent/internal/service/docker_service/bad.go @@ -1,7 +1,7 @@ package docker_service import ( - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" ) type BadDocker struct { diff --git a/internal/agent/service/docker_service/docker.go b/agent/internal/service/docker_service/docker.go similarity index 86% rename from internal/agent/service/docker_service/docker.go rename to agent/internal/service/docker_service/docker.go index 487b1e1..825878c 100644 --- a/internal/agent/service/docker_service/docker.go +++ b/agent/internal/service/docker_service/docker.go @@ -5,7 +5,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" "github.com/rs/zerolog" ) @@ -23,13 +23,13 @@ type DockerService struct { func NewDockerService(api dockerAPI, logger zerolog.Logger) *DockerService { return &DockerService{ dockerClient: api, - log: logger.With().Str("component", "agent.serivce.docker").Logger(), + log: logger.With().Str("component", "internal.serivce.docker").Logger(), } } func (d *DockerService) CheckDockerDaemon(ctx context.Context) error { _, err := d.dockerClient.Ping(ctx) - d.log.Debug().Msg("check docker") + d.log.Debug().Msg("ping docker") return err } diff --git a/internal/agent/service/docker_service/docker_test.go b/agent/internal/service/docker_service/docker_test.go similarity index 94% rename from internal/agent/service/docker_service/docker_test.go rename to agent/internal/service/docker_service/docker_test.go index 25db239..c7b4170 100644 --- a/internal/agent/service/docker_service/docker_test.go +++ b/agent/internal/service/docker_service/docker_test.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog" ) -var testError error = errors.New("test") +var errTest error = errors.New("test") type DockerMock struct { pingErr error @@ -46,11 +46,11 @@ func TestCheckDockerDaemon(t *testing.T) { { name: "docker error", mock: DockerMock{ - pingErr: testError, + pingErr: errTest, containers: nil, containerErr: nil, }, - wantErr: testError, + wantErr: errTest, }, } @@ -98,10 +98,10 @@ func TestContainersList(t *testing.T) { mock: DockerMock{ pingErr: nil, containers: nil, - containerErr: testError, + containerErr: errTest, }, wantLen: 0, - wantErr: testError, + wantErr: errTest, }, { name: "docker empty container", diff --git a/internal/agent/utils/config_yaml/config.go b/agent/internal/utils/config_yaml/config.go similarity index 83% rename from internal/agent/utils/config_yaml/config.go rename to agent/internal/utils/config_yaml/config.go index 9d24106..f8d4b1b 100644 --- a/internal/agent/utils/config_yaml/config.go +++ b/agent/internal/utils/config_yaml/config.go @@ -8,6 +8,9 @@ import ( "gopkg.in/yaml.v3" ) +const MODE = "DEV" +const AgentVersion = "0.0" + type AgentConfig struct { AppName string `yaml:"app_name"` HubConnect struct { @@ -19,7 +22,7 @@ type AgentConfig struct { } func NewConfig() (*AgentConfig, error) { - yamlFile, err := os.ReadFile("agent.dev.yaml") + yamlFile, err := os.ReadFile("config.yaml") if err != nil { return nil, fmt.Errorf("failed open file: %v", err) } @@ -42,7 +45,11 @@ func (c *AgentConfig) GetLogLevel() zerolog.Level { } func (c *AgentConfig) GetMode() string { - return "DEV" + return MODE +} + +func (c *AgentConfig) GetAgentVersion() string { + return AgentVersion } func (c *AgentConfig) GetGRPCAddress() string { diff --git a/internal/agent/utils/config_yaml/config_yaml_test.go b/agent/internal/utils/config_yaml/config_yaml_test.go similarity index 100% rename from internal/agent/utils/config_yaml/config_yaml_test.go rename to agent/internal/utils/config_yaml/config_yaml_test.go diff --git a/internal/agent/utils/settings/settings.go b/agent/internal/utils/settings/settings.go similarity index 69% rename from internal/agent/utils/settings/settings.go rename to agent/internal/utils/settings/settings.go index 1632780..35d7418 100644 --- a/internal/agent/utils/settings/settings.go +++ b/agent/internal/utils/settings/settings.go @@ -13,7 +13,7 @@ type Settings struct { path string } -func ReadSettings(path string) (*Settings, error) { +func ReadSettings(path string) (sett *Settings, err error) { if path == "" { homeDir, err := os.UserHomeDir() if err != nil { @@ -22,7 +22,7 @@ func ReadSettings(path string) (*Settings, error) { path = filepath.Join(homeDir, ".config", "homeops") } - err := os.Mkdir(path, 0755) + err = os.Mkdir(path, 0755) if err != nil { if !errors.Is(err, os.ErrExist) { return nil, err @@ -39,7 +39,12 @@ func ReadSettings(path string) (*Settings, error) { return nil, err } } else { - defer file.Close() + defer func() { + closeErr := file.Close() + if err == nil { + err = closeErr + } + }() err = json.NewDecoder(file).Decode(&settings) if err != nil && !errors.Is(err, io.EOF) { return nil, err @@ -51,12 +56,19 @@ func ReadSettings(path string) (*Settings, error) { return &settings, nil } -func (s *Settings) Insert(sett Settings) error { +func (s *Settings) InsertAgentID(agentID string) (err 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() + defer func() { + closeErr := file.Close() + if err == nil { + err = closeErr + } + }() + + sett := Settings{AgentID: agentID} if err = json.NewEncoder(file).Encode(sett); err != nil { return err @@ -64,3 +76,7 @@ func (s *Settings) Insert(sett Settings) error { return nil } + +func (s *Settings) GetAgentID() string { + return s.AgentID +} diff --git a/api/gen/homeops/hub.pb.go b/api/gen/homeops/hub.pb.go index 284f28c..03f9fcc 100644 --- a/api/gen/homeops/hub.pb.go +++ b/api/gen/homeops/hub.pb.go @@ -322,6 +322,436 @@ func (x *RegisterAgentResponse) GetAgentId() string { return "" } +type ServerCommandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Args map[string]string `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + TimeoutSeconds int64 `protobuf:"varint,4,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServerCommandRequest) Reset() { + *x = ServerCommandRequest{} + mi := &file_homeops_hub_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServerCommandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerCommandRequest) ProtoMessage() {} + +func (x *ServerCommandRequest) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerCommandRequest.ProtoReflect.Descriptor instead. +func (*ServerCommandRequest) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{5} +} + +func (x *ServerCommandRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ServerCommandRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ServerCommandRequest) GetArgs() map[string]string { + if x != nil { + return x.Args + } + return nil +} + +func (x *ServerCommandRequest) GetTimeoutSeconds() int64 { + if x != nil { + return x.TimeoutSeconds + } + return 0 +} + +type AgentEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + // Types that are valid to be assigned to Event: + // + // *AgentEvent_Heartbeat + // *AgentEvent_CommandResponse + // *AgentEvent_Alert + Event isAgentEvent_Event `protobuf_oneof:"event"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentEvent) Reset() { + *x = AgentEvent{} + mi := &file_homeops_hub_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentEvent) ProtoMessage() {} + +func (x *AgentEvent) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentEvent.ProtoReflect.Descriptor instead. +func (*AgentEvent) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{6} +} + +func (x *AgentEvent) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *AgentEvent) GetEvent() isAgentEvent_Event { + if x != nil { + return x.Event + } + return nil +} + +func (x *AgentEvent) GetHeartbeat() *Heartbeat { + if x != nil { + if x, ok := x.Event.(*AgentEvent_Heartbeat); ok { + return x.Heartbeat + } + } + return nil +} + +func (x *AgentEvent) GetCommandResponse() *CommandResponse { + if x != nil { + if x, ok := x.Event.(*AgentEvent_CommandResponse); ok { + return x.CommandResponse + } + } + return nil +} + +func (x *AgentEvent) GetAlert() *Alert { + if x != nil { + if x, ok := x.Event.(*AgentEvent_Alert); ok { + return x.Alert + } + } + return nil +} + +type isAgentEvent_Event interface { + isAgentEvent_Event() +} + +type AgentEvent_Heartbeat struct { + Heartbeat *Heartbeat `protobuf:"bytes,2,opt,name=heartbeat,proto3,oneof"` +} + +type AgentEvent_CommandResponse struct { + CommandResponse *CommandResponse `protobuf:"bytes,3,opt,name=command_response,json=commandResponse,proto3,oneof"` +} + +type AgentEvent_Alert struct { + Alert *Alert `protobuf:"bytes,4,opt,name=alert,proto3,oneof"` +} + +func (*AgentEvent_Heartbeat) isAgentEvent_Event() {} + +func (*AgentEvent_CommandResponse) isAgentEvent_Event() {} + +func (*AgentEvent_Alert) isAgentEvent_Event() {} + +type Heartbeat struct { + state protoimpl.MessageState `protogen:"open.v1"` + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Metrics *SystemMetrics `protobuf:"bytes,2,opt,name=metrics,proto3" json:"metrics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Heartbeat) Reset() { + *x = Heartbeat{} + mi := &file_homeops_hub_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Heartbeat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Heartbeat) ProtoMessage() {} + +func (x *Heartbeat) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. +func (*Heartbeat) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{7} +} + +func (x *Heartbeat) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Heartbeat) GetMetrics() *SystemMetrics { + if x != nil { + return x.Metrics + } + return nil +} + +type SystemMetrics struct { + state protoimpl.MessageState `protogen:"open.v1"` + CpuUsage float32 `protobuf:"fixed32,1,opt,name=cpu_usage,json=cpuUsage,proto3" json:"cpu_usage,omitempty"` + MemoryUsage float32 `protobuf:"fixed32,2,opt,name=memory_usage,json=memoryUsage,proto3" json:"memory_usage,omitempty"` + DiskUsage float32 `protobuf:"fixed32,3,opt,name=disk_usage,json=diskUsage,proto3" json:"disk_usage,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SystemMetrics) Reset() { + *x = SystemMetrics{} + mi := &file_homeops_hub_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SystemMetrics) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemMetrics) ProtoMessage() {} + +func (x *SystemMetrics) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemMetrics.ProtoReflect.Descriptor instead. +func (*SystemMetrics) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{8} +} + +func (x *SystemMetrics) GetCpuUsage() float32 { + if x != nil { + return x.CpuUsage + } + return 0 +} + +func (x *SystemMetrics) GetMemoryUsage() float32 { + if x != nil { + return x.MemoryUsage + } + return 0 +} + +func (x *SystemMetrics) GetDiskUsage() float32 { + if x != nil { + return x.DiskUsage + } + return 0 +} + +type CommandResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` + Output string `protobuf:"bytes,3,opt,name=output,proto3" json:"output,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + ExecTimeMs int64 `protobuf:"varint,5,opt,name=exec_time_ms,json=execTimeMs,proto3" json:"exec_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommandResponse) Reset() { + *x = CommandResponse{} + mi := &file_homeops_hub_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommandResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandResponse) ProtoMessage() {} + +func (x *CommandResponse) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandResponse.ProtoReflect.Descriptor instead. +func (*CommandResponse) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{9} +} + +func (x *CommandResponse) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *CommandResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *CommandResponse) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + +func (x *CommandResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *CommandResponse) GetExecTimeMs() int64 { + if x != nil { + return x.ExecTimeMs + } + return 0 +} + +type Alert struct { + state protoimpl.MessageState `protogen:"open.v1"` + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"` + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Alert) Reset() { + *x = Alert{} + mi := &file_homeops_hub_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Alert) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Alert) ProtoMessage() {} + +func (x *Alert) ProtoReflect() protoreflect.Message { + mi := &file_homeops_hub_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Alert.ProtoReflect.Descriptor instead. +func (*Alert) Descriptor() ([]byte, []int) { + return file_homeops_hub_proto_rawDescGZIP(), []int{10} +} + +func (x *Alert) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Alert) GetLevel() string { + if x != nil { + return x.Level + } + return "" +} + +func (x *Alert) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Alert) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + var File_homeops_hub_proto protoreflect.FileDescriptor const file_homeops_hub_proto_rawDesc = "" + @@ -350,10 +780,49 @@ const file_homeops_hub_proto_rawDesc = "" + "\x06reason\x18\x04 \x01(\tR\x06reason\"n\n" + "\x15RegisterAgentResponse\x12:\n" + "\x19heartbeat_interval_second\x18\x01 \x01(\x03R\x17heartbeatIntervalSecond\x12\x19\n" + - "\bagent_id\x18\x02 \x01(\tR\aagentId2x\n" + + "\bagent_id\x18\x02 \x01(\tR\aagentId\"\xe0\x01\n" + + "\x14ServerCommandRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x123\n" + + "\x04args\x18\x03 \x03(\v2\x1f.ServerCommandRequest.ArgsEntryR\x04args\x12'\n" + + "\x0ftimeout_seconds\x18\x04 \x01(\x03R\x0etimeoutSeconds\x1a7\n" + + "\tArgsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbb\x01\n" + + "\n" + + "AgentEvent\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12*\n" + + "\theartbeat\x18\x02 \x01(\v2\n" + + ".HeartbeatH\x00R\theartbeat\x12=\n" + + "\x10command_response\x18\x03 \x01(\v2\x10.CommandResponseH\x00R\x0fcommandResponse\x12\x1e\n" + + "\x05alert\x18\x04 \x01(\v2\x06.AlertH\x00R\x05alertB\a\n" + + "\x05event\"S\n" + + "\tHeartbeat\x12\x1c\n" + + "\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12(\n" + + "\ametrics\x18\x02 \x01(\v2\x0e.SystemMetricsR\ametrics\"n\n" + + "\rSystemMetrics\x12\x1b\n" + + "\tcpu_usage\x18\x01 \x01(\x02R\bcpuUsage\x12!\n" + + "\fmemory_usage\x18\x02 \x01(\x02R\vmemoryUsage\x12\x1d\n" + + "\n" + + "disk_usage\x18\x03 \x01(\x02R\tdiskUsage\"\x9a\x01\n" + + "\x0fCommandResponse\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x18\n" + + "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x16\n" + + "\x06output\x18\x03 \x01(\tR\x06output\x12\x14\n" + + "\x05error\x18\x04 \x01(\tR\x05error\x12 \n" + + "\fexec_time_ms\x18\x05 \x01(\x03R\n" + + "execTimeMs\"s\n" + + "\x05Alert\x12\x1c\n" + + "\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12\x14\n" + + "\x05level\x18\x02 \x01(\tR\x05level\x12\x14\n" + + "\x05title\x18\x03 \x01(\tR\x05title\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription2\xb6\x01\n" + "\x03Hub\x12/\n" + "\x04Ping\x12\x16.google.protobuf.Empty\x1a\r.PongResponse\"\x00\x12@\n" + - "\rRegisterAgent\x12\x15.RegisterAgentRequest\x1a\x16.RegisterAgentResponse\"\x00B HostInfo - 3, // 1: RegisterAgentRequest.capability:type_name -> Capability - 5, // 2: Hub.Ping:input_type -> google.protobuf.Empty - 1, // 3: Hub.RegisterAgent:input_type -> RegisterAgentRequest - 0, // 4: Hub.Ping:output_type -> PongResponse - 4, // 5: Hub.RegisterAgent:output_type -> RegisterAgentResponse - 4, // [4:6] is the sub-list for method output_type - 2, // [2:4] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 2, // 0: RegisterAgentRequest.host:type_name -> HostInfo + 3, // 1: RegisterAgentRequest.capability:type_name -> Capability + 11, // 2: ServerCommandRequest.args:type_name -> ServerCommandRequest.ArgsEntry + 7, // 3: AgentEvent.heartbeat:type_name -> Heartbeat + 9, // 4: AgentEvent.command_response:type_name -> CommandResponse + 10, // 5: AgentEvent.alert:type_name -> Alert + 8, // 6: Heartbeat.metrics:type_name -> SystemMetrics + 12, // 7: Hub.Ping:input_type -> google.protobuf.Empty + 1, // 8: Hub.RegisterAgent:input_type -> RegisterAgentRequest + 6, // 9: Hub.StreamConnection:input_type -> AgentEvent + 0, // 10: Hub.Ping:output_type -> PongResponse + 4, // 11: Hub.RegisterAgent:output_type -> RegisterAgentResponse + 5, // 12: Hub.StreamConnection:output_type -> ServerCommandRequest + 10, // [10:13] is the sub-list for method output_type + 7, // [7:10] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_homeops_hub_proto_init() } @@ -395,13 +878,18 @@ func file_homeops_hub_proto_init() { if File_homeops_hub_proto != nil { return } + file_homeops_hub_proto_msgTypes[6].OneofWrappers = []any{ + (*AgentEvent_Heartbeat)(nil), + (*AgentEvent_CommandResponse)(nil), + (*AgentEvent_Alert)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_homeops_hub_proto_rawDesc), len(file_homeops_hub_proto_rawDesc)), NumEnums: 0, - NumMessages: 5, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/homeops/hub_grpc.pb.go b/api/gen/homeops/hub_grpc.pb.go index e032d6c..d689399 100644 --- a/api/gen/homeops/hub_grpc.pb.go +++ b/api/gen/homeops/hub_grpc.pb.go @@ -20,8 +20,9 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Hub_Ping_FullMethodName = "/Hub/Ping" - Hub_RegisterAgent_FullMethodName = "/Hub/RegisterAgent" + Hub_Ping_FullMethodName = "/Hub/Ping" + Hub_RegisterAgent_FullMethodName = "/Hub/RegisterAgent" + Hub_StreamConnection_FullMethodName = "/Hub/StreamConnection" ) // HubClient is the client API for Hub service. @@ -30,6 +31,7 @@ const ( type HubClient interface { Ping(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PongResponse, error) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) + StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentEvent, ServerCommandRequest], error) } type hubClient struct { @@ -60,12 +62,26 @@ func (c *hubClient) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, return out, nil } +func (c *hubClient) StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentEvent, ServerCommandRequest], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &Hub_ServiceDesc.Streams[0], Hub_StreamConnection_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[AgentEvent, ServerCommandRequest]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Hub_StreamConnectionClient = grpc.BidiStreamingClient[AgentEvent, ServerCommandRequest] + // HubServer is the server API for Hub service. // All implementations must embed UnimplementedHubServer // for forward compatibility. type HubServer interface { Ping(context.Context, *emptypb.Empty) (*PongResponse, error) RegisterAgent(context.Context, *RegisterAgentRequest) (*RegisterAgentResponse, error) + StreamConnection(grpc.BidiStreamingServer[AgentEvent, ServerCommandRequest]) error mustEmbedUnimplementedHubServer() } @@ -82,6 +98,9 @@ func (UnimplementedHubServer) Ping(context.Context, *emptypb.Empty) (*PongRespon func (UnimplementedHubServer) RegisterAgent(context.Context, *RegisterAgentRequest) (*RegisterAgentResponse, error) { return nil, status.Error(codes.Unimplemented, "method RegisterAgent not implemented") } +func (UnimplementedHubServer) StreamConnection(grpc.BidiStreamingServer[AgentEvent, ServerCommandRequest]) error { + return status.Error(codes.Unimplemented, "method StreamConnection not implemented") +} func (UnimplementedHubServer) mustEmbedUnimplementedHubServer() {} func (UnimplementedHubServer) testEmbeddedByValue() {} @@ -139,6 +158,13 @@ func _Hub_RegisterAgent_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Hub_StreamConnection_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(HubServer).StreamConnection(&grpc.GenericServerStream[AgentEvent, ServerCommandRequest]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Hub_StreamConnectionServer = grpc.BidiStreamingServer[AgentEvent, ServerCommandRequest] + // Hub_ServiceDesc is the grpc.ServiceDesc for Hub service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -155,6 +181,13 @@ var Hub_ServiceDesc = grpc.ServiceDesc{ Handler: _Hub_RegisterAgent_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamConnection", + Handler: _Hub_StreamConnection_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, Metadata: "homeops/hub.proto", } diff --git a/api/proto/homeops/hub.proto b/api/proto/homeops/hub.proto index e7f32d9..301b992 100644 --- a/api/proto/homeops/hub.proto +++ b/api/proto/homeops/hub.proto @@ -7,6 +7,7 @@ option go_package = "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops;homeops" service Hub { rpc Ping (google.protobuf.Empty) returns (PongResponse) {} rpc RegisterAgent (RegisterAgentRequest) returns (RegisterAgentResponse) {} + rpc StreamConnection (stream AgentEvent) returns (stream ServerCommandRequest) {} } message PongResponse { @@ -37,4 +38,47 @@ message Capability { message RegisterAgentResponse { int64 heartbeat_interval_second = 1; string agent_id = 2; +} + +message ServerCommandRequest { + string request_id = 1; + string name = 2; + map args = 3; + int64 timeout_seconds = 4; +} + +message AgentEvent { + string agent_id = 1; + + oneof event { + Heartbeat heartbeat = 2; + CommandResponse command_response = 3; + Alert alert = 4; + } +} + +message Heartbeat { + int64 timestamp = 1; + SystemMetrics metrics = 2; +} + +message SystemMetrics { + float cpu_usage = 1; + float memory_usage = 2; + float disk_usage = 3; +} + +message CommandResponse { + string request_id = 1; + bool success = 2; + string output = 3; + string error = 4; + int64 exec_time_ms = 5; +} + +message Alert { + int64 timestamp = 1; + string level = 2; + string title = 3; + string description = 4; } \ No newline at end of file diff --git a/cmd/telegram/main.go b/cmd/telegram/main.go deleted file mode 100644 index 06ab7d0..0000000 --- a/cmd/telegram/main.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/cmd/hub/main.go b/hub/cmd/main.go similarity index 50% rename from cmd/hub/main.go rename to hub/cmd/main.go index afc06ec..5e2033c 100644 --- a/cmd/hub/main.go +++ b/hub/cmd/main.go @@ -1,6 +1,8 @@ package main -import "github.com/lorsanstand/HomeOps-Hub/internal/hub/app" +import ( + "github.com/lorsanstand/HomeOps-Hub/hub/internal/app" +) func main() { start := app.NewApp() diff --git a/internal/hub/app/app.go b/hub/internal/app/app.go similarity index 80% rename from internal/hub/app/app.go rename to hub/internal/app/app.go index 56652b0..c1db0e3 100644 --- a/internal/hub/app/app.go +++ b/hub/internal/app/app.go @@ -8,13 +8,13 @@ import ( "net" "github.com/jackc/pgx/v5/pgxpool" - hubdir "github.com/lorsanstand/HomeOps-Hub/internal/hub" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/migrator" - grpcserv "github.com/lorsanstand/HomeOps-Hub/internal/hub/rpc" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/service/hub_service" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/store" - "github.com/lorsanstand/HomeOps-Hub/internal/shared/config" - "github.com/lorsanstand/HomeOps-Hub/internal/shared/log" + hubdir "github.com/lorsanstand/HomeOps-Hub/hub/internal" + "github.com/lorsanstand/HomeOps-Hub/hub/internal/migrator" + grpcserv "github.com/lorsanstand/HomeOps-Hub/hub/internal/rpc" + "github.com/lorsanstand/HomeOps-Hub/hub/internal/service/hub_service" + "github.com/lorsanstand/HomeOps-Hub/hub/internal/store" + "github.com/lorsanstand/HomeOps-Hub/shared/config" + "github.com/lorsanstand/HomeOps-Hub/shared/log" "github.com/rs/zerolog" ) @@ -27,6 +27,7 @@ func NewApp() *App { cfg, err := config.NewConfig() if err != nil { standartlog.Fatalf("failed get config: %v", err) + return nil } logger := log.NewLogger(cfg) @@ -42,7 +43,6 @@ func (a *App) Run() { a.log.Error().Err(err).Msg("failed to connect to the database for migrations") return } - defer migratePGConn.Close() mgrt, err := migrator.NewMigrator(hubdir.MigrationsFS, "migrations") if err != nil { @@ -56,7 +56,9 @@ func (a *App) Run() { return } a.log.Info().Msg("migrations applied successfully") - migratePGConn.Close() + if err := migratePGConn.Close(); err != nil { + a.log.Warn().Err(err).Msg("failed to close migrate postgres connection") + } a.log.Info().Msg("creating database connection pool") pool, err := pgxpool.New(ctx, a.cfg.GetURLPostgres()) @@ -80,7 +82,6 @@ func (a *App) Run() { func (a *App) hubServe(hubService *hub_service.HubService) error { address := fmt.Sprintf("0.0.0.0:%v", a.cfg.Port) - a.log.Info().Str("address", address).Msg("starting gRPC server") server := grpcserv.NewHubHandler(hubService, a.log) diff --git a/internal/hub/domain/structure.go b/hub/internal/domain/structure.go similarity index 89% rename from internal/hub/domain/structure.go rename to hub/internal/domain/structure.go index 795fe89..79bea4a 100644 --- a/internal/hub/domain/structure.go +++ b/hub/internal/domain/structure.go @@ -3,7 +3,7 @@ package domain import ( "time" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" ) type CreateAgentModel struct { diff --git a/internal/hub/embed.go b/hub/internal/embed.go similarity index 80% rename from internal/hub/embed.go rename to hub/internal/embed.go index 1938b80..e4740ca 100644 --- a/internal/hub/embed.go +++ b/hub/internal/embed.go @@ -1,4 +1,4 @@ -package hub +package internal import "embed" diff --git a/internal/hub/migrations/20260415151037_create_agent_table.down.sql b/hub/internal/migrations/20260415151037_create_agent_table.down.sql similarity index 100% rename from internal/hub/migrations/20260415151037_create_agent_table.down.sql rename to hub/internal/migrations/20260415151037_create_agent_table.down.sql diff --git a/internal/hub/migrations/20260415151037_create_agent_table.up.sql b/hub/internal/migrations/20260415151037_create_agent_table.up.sql similarity index 100% rename from internal/hub/migrations/20260415151037_create_agent_table.up.sql rename to hub/internal/migrations/20260415151037_create_agent_table.up.sql diff --git a/internal/hub/migrator/migrator.go b/hub/internal/migrator/migrator.go similarity index 87% rename from internal/hub/migrator/migrator.go rename to hub/internal/migrator/migrator.go index 22338f8..159bd8e 100644 --- a/internal/hub/migrator/migrator.go +++ b/hub/internal/migrator/migrator.go @@ -5,6 +5,7 @@ import ( "embed" "errors" "fmt" + _ "github.com/jackc/pgx/v5/stdlib" "github.com/golang-migrate/migrate/v4" @@ -25,7 +26,7 @@ func NewMigrator(sqlFiles embed.FS, dirname string) (*Migrator, error) { return &Migrator{srcDriver: d}, nil } -func (m *Migrator) ApplyMigrations(db *sql.DB) error { +func (m *Migrator) ApplyMigrations(db *sql.DB) (err error) { driver, err := postgres.WithInstance(db, &postgres.Config{}) if err != nil { return fmt.Errorf("unable to create db instance: %w", err) @@ -36,7 +37,12 @@ func (m *Migrator) ApplyMigrations(db *sql.DB) error { return fmt.Errorf("unable to create migration: %w", err) } - defer migrator.Close() + defer func() { + closeErr, _ := migrator.Close() + if err == nil { + err = closeErr + } + }() if err = migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { return fmt.Errorf("unable to apply migrations: %w", err) diff --git a/internal/hub/rpc/server.go b/hub/internal/rpc/server.go similarity index 83% rename from internal/hub/rpc/server.go rename to hub/internal/rpc/server.go index 1a5aade..d9de289 100644 --- a/internal/hub/rpc/server.go +++ b/hub/internal/rpc/server.go @@ -4,7 +4,7 @@ import ( "context" pb "github.com/lorsanstand/HomeOps-Hub/api/gen/homeops" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" @@ -38,13 +38,13 @@ func (h *HubHandler) Ping(ctx context.Context, _ *emptypb.Empty) (*pb.PongRespon } func (h *HubHandler) RegisterAgent(ctx context.Context, request *pb.RegisterAgentRequest) (*pb.RegisterAgentResponse, error) { - h.log.Debug().Str("agentId", request.AgentId).Str("agentName", request.AgentName).Msg("register agent request received") + h.log.Debug().Str("agentID", request.AgentId).Str("agentName", request.AgentName).Msg("register agent request received") data := domain.ToDomainAgentRequest(request) resp, err := h.hub.RegisterAgent(ctx, data) if err != nil { - h.log.Error().Err(err).Str("agentId", request.AgentId).Msg("register agent request failed") + h.log.Error().Err(err).Str("agentID", request.AgentId).Msg("register agent request failed") return domain.ToGRPCAgentResponse(resp), err } - h.log.Debug().Str("agentId", resp.AgentID).Msg("register agent request completed") + h.log.Info().Str("agentID", resp.AgentID).Msg("register agent request completed") return domain.ToGRPCAgentResponse(resp), nil } diff --git a/internal/hub/service/hub_service/hub.go b/hub/internal/service/hub_service/hub.go similarity index 51% rename from internal/hub/service/hub_service/hub.go rename to hub/internal/service/hub_service/hub.go index aa86b1a..00d7181 100644 --- a/internal/hub/service/hub_service/hub.go +++ b/hub/internal/service/hub_service/hub.go @@ -6,12 +6,14 @@ import ( "errors" "fmt" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" - domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/utils/hasher" + domainHub "github.com/lorsanstand/HomeOps-Hub/hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/hub/internal/utils/hasher" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" "github.com/rs/zerolog" ) +const HEARTBEAT = 5 + type Store interface { NewAgent(ctx context.Context, agent domainHub.CreateAgentModel) error GetAgentByAgentID(ctx context.Context, AgentID string) (domainHub.AgentModel, error) @@ -28,43 +30,37 @@ func NewHubService(store Store, logger zerolog.Logger) *HubService { } func (h *HubService) RegisterAgent(ctx context.Context, data domain.RegisterAgentRequest) (domain.RegisterAgentResponse, error) { - h.log.Debug().Str("agentId", data.AgentId).Str("agentName", data.AgentName).Msg("started registering agent") - agent, err := h.store.GetAgentByAgentID(ctx, data.AgentId) + h.log.Debug().Str("agentID", data.AgentID).Str("agentName", data.AgentName).Msg("started registering agent") + agent, err := h.store.GetAgentByAgentID(ctx, data.AgentID) if err != nil && !errors.Is(err, sql.ErrNoRows) { - h.log.Error().Err(err).Str("agentId", data.AgentId).Msg("failed to get agent from database") return domain.RegisterAgentResponse{}, fmt.Errorf("failed select agent to db: %w", err) } - if data.AgentId != "" && !errors.Is(err, sql.ErrNoRows) { - h.log.Debug().Str("agentId", agent.AgentID).Str("agentName", data.AgentName).Msg("agent exists, updating") + if data.AgentID != "" && !errors.Is(err, sql.ErrNoRows) { + h.log.Debug().Str("agentID", agent.AgentID).Str("agentName", data.AgentName).Msg("agent exists, updating") - data.AgentId = agent.AgentID + data.AgentID = agent.AgentID agentStore := toCreateAgentModel(data) if err := h.store.UpdateAgentByID(ctx, agent.ID, agentStore); err != nil { - h.log.Error().Err(err).Str("agentId", agent.AgentID).Msg("failed to update agent in database") - return domain.RegisterAgentResponse{}, err + return domain.RegisterAgentResponse{}, fmt.Errorf("update agent in db: %w", err) } - h.log.Info().Str("agentId", agent.AgentID).Msg("agent updated successfully") - return domain.RegisterAgentResponse{AgentID: agent.AgentID, Heartbeat: 5}, nil + h.log.Debug().Str("agentId", agent.AgentID).Msg("agent updated successfully") + return domain.RegisterAgentResponse{AgentID: agent.AgentID, Heartbeat: HEARTBEAT}, nil } AgentID, err := hasher.MakeID(data.Host, data.AgentName) if err != nil { - h.log.Error().Err(err).Str("agentName", data.AgentName).Str("hostname", data.Host.Hostname).Msg("failed to generate agent id") - return domain.RegisterAgentResponse{}, err + return domain.RegisterAgentResponse{}, fmt.Errorf("generate agent ID: %w", err) } - data.AgentId = AgentID + data.AgentID = AgentID agentStore := toCreateAgentModel(data) if err := h.store.NewAgent(ctx, agentStore); err != nil { - h.log.Error().Err(err).Str("agentId", AgentID).Str("agentName", data.AgentName).Msg("failed to create new agent in database") - return domain.RegisterAgentResponse{}, err + return domain.RegisterAgentResponse{}, fmt.Errorf("insert new agent: %w", err) } - - h.log.Info().Str("agentId", AgentID).Str("agentName", data.AgentName).Str("hostname", data.Host.Hostname).Msg("agent registered successfully") - return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: 5}, nil + return domain.RegisterAgentResponse{AgentID: AgentID, Heartbeat: HEARTBEAT}, nil } diff --git a/internal/hub/service/hub_service/mapper.go b/hub/internal/service/hub_service/mapper.go similarity index 71% rename from internal/hub/service/hub_service/mapper.go rename to hub/internal/service/hub_service/mapper.go index 6a020d8..9013dab 100644 --- a/internal/hub/service/hub_service/mapper.go +++ b/hub/internal/service/hub_service/mapper.go @@ -1,13 +1,13 @@ package hub_service import ( - "github.com/lorsanstand/HomeOps-Hub/internal/domain" - domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" + domainHub "github.com/lorsanstand/HomeOps-Hub/hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" ) func toCreateAgentModel(agent domain.RegisterAgentRequest) domainHub.CreateAgentModel { return domainHub.CreateAgentModel{ - AgentID: agent.AgentId, + AgentID: agent.AgentID, AgentName: agent.AgentName, Architecture: agent.Host.Arch, System: agent.Host.System, diff --git a/internal/hub/store/mapper.go b/hub/internal/store/mapper.go similarity index 66% rename from internal/hub/store/mapper.go rename to hub/internal/store/mapper.go index cbce0c3..b843735 100644 --- a/internal/hub/store/mapper.go +++ b/hub/internal/store/mapper.go @@ -3,36 +3,36 @@ package store import ( "encoding/json" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" - domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" + domainHub "github.com/lorsanstand/HomeOps-Hub/hub/internal/domain" + gen2 "github.com/lorsanstand/HomeOps-Hub/hub/internal/store/sqlc/gen" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" ) -func toDBAgent(agent domainHub.CreateAgentModel) gen.CreateAgentParams { - return gen.CreateAgentParams{ +func toDBAgent(agent domainHub.CreateAgentModel) gen2.CreateAgentParams { + return gen2.CreateAgentParams{ AgentID: agent.AgentID, AgentName: &agent.AgentName, Architecture: agent.Architecture, System: agent.System, Hostname: agent.Hostname, Version: agent.Version, - Capabilities: toJsonCapabilities(agent.Capabilities), + Capabilities: toJSONCapabilities(agent.Capabilities), } } -func toUpdateDBAgent(agent domainHub.CreateAgentModel) gen.UpdateAgentByIDParams { - return gen.UpdateAgentByIDParams{ +func toUpdateDBAgent(agent domainHub.CreateAgentModel) gen2.UpdateAgentByIDParams { + return gen2.UpdateAgentByIDParams{ AgentID: agent.AgentID, AgentName: &agent.AgentName, Architecture: agent.Architecture, System: agent.System, Hostname: agent.Hostname, Version: agent.Version, - Capabilities: toJsonCapabilities(agent.Capabilities), + Capabilities: toJSONCapabilities(agent.Capabilities), } } -func toJsonCapabilities(caps []domain.Capability) []byte { +func toJSONCapabilities(caps []domain.Capability) []byte { data, err := json.Marshal(caps) if err != nil { // Note: Error is silently handled - consider logging in production @@ -41,7 +41,7 @@ func toJsonCapabilities(caps []domain.Capability) []byte { return data } -func toAgentModel(dbAgent gen.Agent) domainHub.AgentModel { +func toAgentModel(dbAgent gen2.Agent) domainHub.AgentModel { var dbAgentName string if dbAgent.AgentName != nil { dbAgentName = *dbAgent.AgentName diff --git a/internal/hub/store/sqlc/gen/agent.sql.go b/hub/internal/store/sqlc/gen/agent.sql.go similarity index 99% rename from internal/hub/store/sqlc/gen/agent.sql.go rename to hub/internal/store/sqlc/gen/agent.sql.go index 6a6e23c..3d55d93 100644 --- a/internal/hub/store/sqlc/gen/agent.sql.go +++ b/hub/internal/store/sqlc/gen/agent.sql.go @@ -1,7 +1,7 @@ // Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.30.0 -// source: agent.sql +// source: cmd.sql package gen diff --git a/internal/hub/store/sqlc/gen/db.go b/hub/internal/store/sqlc/gen/db.go similarity index 100% rename from internal/hub/store/sqlc/gen/db.go rename to hub/internal/store/sqlc/gen/db.go diff --git a/internal/hub/store/sqlc/gen/models.go b/hub/internal/store/sqlc/gen/models.go similarity index 100% rename from internal/hub/store/sqlc/gen/models.go rename to hub/internal/store/sqlc/gen/models.go diff --git a/internal/hub/store/sqlc/queries/agent.sql b/hub/internal/store/sqlc/queries/agent.sql similarity index 100% rename from internal/hub/store/sqlc/queries/agent.sql rename to hub/internal/store/sqlc/queries/agent.sql diff --git a/internal/hub/store/store.go b/hub/internal/store/store.go similarity index 86% rename from internal/hub/store/store.go rename to hub/internal/store/store.go index 02936d4..851eec9 100644 --- a/internal/hub/store/store.go +++ b/hub/internal/store/store.go @@ -4,8 +4,8 @@ import ( "context" "github.com/jackc/pgx/v5/pgxpool" - domainHub "github.com/lorsanstand/HomeOps-Hub/internal/hub/domain" - "github.com/lorsanstand/HomeOps-Hub/internal/hub/store/sqlc/gen" + domainHub "github.com/lorsanstand/HomeOps-Hub/hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/hub/internal/store/sqlc/gen" ) type HubStore struct { diff --git a/internal/hub/utils/hasher/id.go b/hub/internal/utils/hasher/id.go similarity index 90% rename from internal/hub/utils/hasher/id.go rename to hub/internal/utils/hasher/id.go index e28b504..21f94f8 100644 --- a/internal/hub/utils/hasher/id.go +++ b/hub/internal/utils/hasher/id.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "fmt" - "github.com/lorsanstand/HomeOps-Hub/internal/domain" + "github.com/lorsanstand/HomeOps-Hub/shared/domain" ) func newSalt(n int) ([]byte, error) { diff --git a/internal/agent/service/agent_service/agent.go b/internal/agent/service/agent_service/agent.go deleted file mode 100644 index af9f050..0000000 --- a/internal/agent/service/agent_service/agent.go +++ /dev/null @@ -1,60 +0,0 @@ -package agent_service - -import ( - "context" - "fmt" - - "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/config_yaml" - "github.com/lorsanstand/HomeOps-Hub/internal/agent/utils/settings" - "github.com/lorsanstand/HomeOps-Hub/internal/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", "agent.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 agent") - return - } - - if err = a.settings.Insert(settings.Settings{AgentID: data.AgentID}); err != nil { - a.log.Warn().Err(err).Msg("failed to save agent id") - } - fmt.Println(data) -} diff --git a/internal/agent/service/agent_service/agent_test.go b/internal/agent/service/agent_service/agent_test.go deleted file mode 100644 index b6852ac..0000000 --- a/internal/agent/service/agent_service/agent_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package agent_service - -import ( - "context" - - "github.com/lorsanstand/HomeOps-Hub/internal/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 -} diff --git a/internal/shared/config/config.go b/shared/config/config.go similarity index 100% rename from internal/shared/config/config.go rename to shared/config/config.go diff --git a/internal/shared/config/config_test.go b/shared/config/config_test.go similarity index 100% rename from internal/shared/config/config_test.go rename to shared/config/config_test.go diff --git a/internal/domain/agent.go b/shared/domain/agent.go similarity index 94% rename from internal/domain/agent.go rename to shared/domain/agent.go index 8a5f062..2a24ae5 100644 --- a/internal/domain/agent.go +++ b/shared/domain/agent.go @@ -1,7 +1,7 @@ package domain type RegisterAgentRequest struct { - AgentId string + AgentID string AgentName string AgentVersion string Host HostInfo diff --git a/internal/domain/mapper.go b/shared/domain/mapper.go similarity index 97% rename from internal/domain/mapper.go rename to shared/domain/mapper.go index 2812e8e..587d361 100644 --- a/internal/domain/mapper.go +++ b/shared/domain/mapper.go @@ -10,7 +10,7 @@ func ToDomainAgentRequest(request *pb.RegisterAgentRequest) RegisterAgentRequest } return RegisterAgentRequest{ - AgentId: request.AgentId, + AgentID: request.AgentId, AgentName: request.AgentName, Host: HostInfo{ System: request.Host.System, @@ -53,7 +53,7 @@ func ToDomainCapabilities(capability []*pb.Capability) []Capability { func ToGRPCAgentRequest(request RegisterAgentRequest) pb.RegisterAgentRequest { return pb.RegisterAgentRequest{ - AgentId: request.AgentId, + AgentId: request.AgentID, AgentName: request.AgentName, Host: &pb.HostInfo{ Hostname: request.Host.Hostname, diff --git a/internal/shared/log/init.go b/shared/log/init.go similarity index 100% rename from internal/shared/log/init.go rename to shared/log/init.go diff --git a/sqlc.yaml b/sqlc.yaml index 4e02b14..5c97fd8 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -2,11 +2,11 @@ version: "2" sql: - engine: "postgresql" - queries: "internal/hub/store/sqlc/queries" - schema: "./internal/hub/migrations/" + queries: "hub/internal/store/sqlc/queries" + schema: "./hub/internal/migrations/" gen: go: sql_package: "pgx/v5" package: "gen" - out: "internal/hub/store/sqlc/gen" + out: "hub/internal/store/sqlc/gen" emit_pointers_for_null_types: true \ No newline at end of file