Skip to content

Commit

Permalink
Added: storage interface
Browse files Browse the repository at this point in the history
  • Loading branch information
capcom6 committed Dec 23, 2023
1 parent a793bc1 commit 088896d
Show file tree
Hide file tree
Showing 16 changed files with 458 additions and 158 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
push:
pull_request:

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
# step 1: checkout repository code
- name: Checkout code into workspace directory
uses: actions/checkout@v4

# step 2: set up go
- name: Set up Go 1.21
uses: actions/setup-go@v4
with:
go-version: ">=1.21"

# step 3: install dependencies
- name: Install all Go dependencies
run: go mod download

# step 4: run test
- name: Run coverage
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
go.uber.org/fx v1.20.1
go.uber.org/zap v1.26.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -15,5 +16,4 @@ require (
go.uber.org/dig v1.17.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions internal/bot/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/capcom6/service-monitor-tgbot/internal/config"
"github.com/capcom6/service-monitor-tgbot/internal/infrastructure"
"github.com/capcom6/service-monitor-tgbot/internal/monitor"
"github.com/capcom6/service-monitor-tgbot/internal/storage"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
Expand All @@ -20,6 +21,7 @@ var Module = fx.Module(
config.Module,
infrastructure.Module,
monitor.Module,
storage.Module,
fx.Provide(NewMessages),
)

Expand Down
135 changes: 4 additions & 131 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,143 +1,16 @@
package config

import (
"fmt"
"math/rand"
"strings"
)

type Config struct {
Telegram Telegram `yaml:"telegram"`
Services []Service `yaml:"services"`
Telegram Telegram `yaml:"telegram"`
// Services []Service `yaml:"services"`
}

type Telegram struct {
Token string `yaml:"token" envconfig:"TELEGRAM__TOKEN" validate:"required"`
ChatID int64 `yaml:"chatId" envconfig:"TELEGRAM__CHAT_ID"`
WebhookURL string `yaml:"webhookUrl" envconfig:"TELEGRAM__WEBHOOK_URL" validate:"required"`
Debug bool `yaml:"debug" envconfig:"TELEGRAM__DEBUG"`
Messages TelegramMessages `yaml:"messages"`
}
type TelegramMessages map[string]string
type HTTPHeader struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}

type HTTPHeaders []HTTPHeader

func (h HTTPHeaders) ToMap() map[string][]string {
m := make(map[string][]string, len(h))

for _, v := range h {
m[v.Name] = append(m[v.Name], v.Value)
}

return m
}

type HTTPGet struct {
TCPSocket `yaml:",inline"`
Scheme string `yaml:"scheme"`
Path string `yaml:"path"`
HTTPHeaders HTTPHeaders `yaml:"httpHeaders"`
}

func (s HTTPGet) IsEmpty() bool {
return s.Host == ""
}

func (h HTTPGet) ApplyDefaultsAndValidate() (HTTPGet, error) {
if h.IsEmpty() {
return h, nil
}

if h.Scheme == "" {
if h.Port == 80 {
h.Scheme = "http"
}
if h.Port == 443 {
h.Scheme = "https"
}
}

if h.Port == 0 {
if h.Scheme == "http" {
h.Port = 80
}
if h.Scheme == "https" {
h.Port = 443
}
}

if h.Scheme != "http" && h.Scheme != "https" {
return h, fmt.Errorf("invalid scheme %s", h.Scheme)
}

if !strings.HasPrefix(h.Path, "/") {
h.Path = "/" + h.Path
}

return h, nil
}

type TCPSocket struct {
Host string `yaml:"host" validate:"required,hostname"`
Port uint16 `yaml:"port"`
}

func (s TCPSocket) IsEmpty() bool {
return s.Host == ""
}

type Service struct {
Id string `yaml:"id"`
Name string `yaml:"name"`
InitialDelaySeconds int `yaml:"initialDelaySeconds"` // пауза перед первым опросом в секундах, по умолчанию: 0; если меньше 0, то используется случайное значение между 0 и `periodSeconds`
PeriodSeconds int `yaml:"periodSeconds"` // период опроса в секундах, по умолчанию: 10
TimeoutSeconds int `yaml:"timeoutSeconds"` // время ожидания ответа в секундах, по кмолчанию: 1
SuccessThreshold int `yaml:"successThreshold"` // количество последовательных успешных соединений для перехода в состояние "в сети", по умолчанию: 1
FailureThreshold int `yaml:"failureThreshold"` // количество последовательных ошибок соединения для перехода в состояние "не в сети", по умолчанию: 3
HTTPGet HTTPGet `yaml:"httpGet,omitempty"`
TCPSocket TCPSocket `yaml:"tcpSocket,omitempty"`
}

func (s Service) ApplyDefaultsAndValidate() (svc Service, err error) {
if s.PeriodSeconds < 0 {
return s, fmt.Errorf("periodSeconds must be gte 0")
}
if s.SuccessThreshold < 0 {
return s, fmt.Errorf("successThreshold must be gte 0")
}
if s.FailureThreshold < 0 {
return s, fmt.Errorf("failureThreshold must be gte 0")
}
if s.HTTPGet.IsEmpty() && s.TCPSocket.IsEmpty() {
return s, fmt.Errorf("one of httpGet or tcpSocket must be filled")
}

if s.HTTPGet, err = s.HTTPGet.ApplyDefaultsAndValidate(); err != nil {
return s, err
}

if s.PeriodSeconds == 0 {
s.PeriodSeconds = 10
}

if s.InitialDelaySeconds < 0 {
s.InitialDelaySeconds = rand.Intn(s.PeriodSeconds + 1)
}

if s.SuccessThreshold == 0 {
s.SuccessThreshold = 1
}

if s.FailureThreshold == 0 {
s.FailureThreshold = 3
}

return s, nil
}

type Monitor struct {
Services []Service `yaml:"services"`
}
type TelegramMessages map[string]string
2 changes: 1 addition & 1 deletion internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ var (
Debug: false,
Messages: defaultTelegramMessages,
},
Services: []Service{},
// Services: []Service{},
}
)
10 changes: 5 additions & 5 deletions internal/config/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ var Module = fx.Module(
fx.Provide(func(cfg Config) Telegram {
return cfg.Telegram
}),
fx.Provide(func(cfg Config) Monitor {
return Monitor{
Services: cfg.Services,
}
}),
// fx.Provide(func(cfg Config) Monitor {
// return Monitor{
// Services: cfg.Services,
// }
// }),
fx.Provide(func(cfg Config) TelegramMessages {
return cfg.Telegram.Messages
}),
Expand Down
36 changes: 20 additions & 16 deletions internal/monitor/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"fmt"
"sync"

"github.com/capcom6/service-monitor-tgbot/internal/config"
"github.com/capcom6/service-monitor-tgbot/internal/monitor/probes"
"github.com/capcom6/service-monitor-tgbot/internal/storage"
"github.com/capcom6/service-monitor-tgbot/pkg/collections"
"go.uber.org/fx"
"go.uber.org/zap"
)
Expand All @@ -22,34 +23,37 @@ var Module = fx.Module(
type MonitorModuleParams struct {
fx.In

Config config.Monitor
Logger *zap.Logger
Storage storage.Storage
Logger *zap.Logger
}

type MonitorModule struct {
Services []config.Service
Storage storage.Storage
Services []storage.Service
Logger *zap.Logger

probes []ProbesChannel
states []state
}

func NewMonitorModule(params MonitorModuleParams) *MonitorModule {
func NewMonitorModule(params MonitorModuleParams) (*MonitorModule, error) {
services, err := params.Storage.Load()
if err != nil {
return nil, fmt.Errorf("failed to load services: %w", err)
}

return &MonitorModule{
Services: params.Config.Services,
Storage: params.Storage,
Services: services,
Logger: params.Logger,
}
}, nil
}

func (m *MonitorModule) Monitor(ctx context.Context) (UpdatesChannel, error) {
m.probes = make([]ProbesChannel, len(m.Services))
m.states = make([]state, len(m.Services))

for i, s := range m.Services {
cfg, err := s.ApplyDefaultsAndValidate()
if err != nil {
return nil, fmt.Errorf("invalid config for %s: %w", s.Name, err)
}
for i, cfg := range m.Services {
mon := NewMonitorService(ServiceMonitorConfig{
HttpGet: probes.HttpGetConfig{
TcpSocketConfig: probes.TcpSocketConfig{
Expand All @@ -58,13 +62,13 @@ func (m *MonitorModule) Monitor(ctx context.Context) (UpdatesChannel, error) {
},
Scheme: cfg.HTTPGet.Scheme,
Path: cfg.HTTPGet.Path,
HTTPHeaders: cfg.HTTPGet.HTTPHeaders.ToMap(),
HTTPHeaders: collections.GroupBy(cfg.HTTPGet.HTTPHeaders, func(h storage.HTTPHeader) (string, string) { return h.Name, h.Value }),
},
TcpSocket: probes.TcpSocketConfig{
Host: cfg.TCPSocket.Host,
Port: cfg.TCPSocket.Port,
},
InitialDelaySeconds: cfg.InitialDelaySeconds,
InitialDelaySeconds: uint16(cfg.InitialDelaySeconds),
PeriodSeconds: cfg.PeriodSeconds,
TimeoutSeconds: cfg.TimeoutSeconds,
})
Expand Down Expand Up @@ -130,15 +134,15 @@ func (m *MonitorModule) updateState(id int, probe error) *ServiceStatus {
}

var upd *ServiceStatus
if !current.Online && current.Probes == service.SuccessThreshold {
if !current.Online && current.Probes == int(service.SuccessThreshold) {
current.Online = true
upd = &ServiceStatus{
Id: service.Id,
Name: service.Name,
State: ServiceOnline,
Error: nil,
}
} else if current.Online && current.Probes == -service.FailureThreshold {
} else if current.Online && current.Probes == -int(service.FailureThreshold) {
current.Online = false
upd = &ServiceStatus{
Id: service.Id,
Expand Down
6 changes: 3 additions & 3 deletions internal/monitor/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
type ServiceMonitorConfig struct {
HttpGet probes.HttpGetConfig
TcpSocket probes.TcpSocketConfig
InitialDelaySeconds int
PeriodSeconds int
TimeoutSeconds int
InitialDelaySeconds uint16
PeriodSeconds uint16
TimeoutSeconds uint16
}

type MonitorService struct {
Expand Down
4 changes: 3 additions & 1 deletion internal/monitor/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package monitor

import "context"
import (
"context"
)

const (
ServiceOnline ServiceState = "online"
Expand Down
18 changes: 18 additions & 0 deletions internal/storage/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package storage

import (
"os"

"go.uber.org/fx"
)

var Module = fx.Module(
"storage",
fx.Provide(func() Storage {
return NewStorageService(
&yamlStorage{
Path: os.Getenv("CONFIG_PATH"),
},
)
}),
)
30 changes: 30 additions & 0 deletions internal/storage/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package storage

type Storage interface {
Load() ([]Service, error)
}

type StorageService struct {
storage Storage
}

func NewStorageService(storage Storage) *StorageService {
return &StorageService{
storage: storage,
}
}

func (s *StorageService) Load() ([]Service, error) {
services, err := s.storage.Load()
if err != nil {
return nil, err
}

for i := range services {
if err := services[i].Validate(); err != nil {
return nil, err
}
}

return services, nil
}
Loading

0 comments on commit 088896d

Please sign in to comment.