Initial commit
continuous-integration/drone Build is passing Details

This commit is contained in:
Tom Neuber 2024-03-07 02:31:54 +01:00
parent e3378775f9
commit e9b2168dc5
Signed by: tom
GPG Key ID: F17EFE4272D89FF6
13 changed files with 653 additions and 0 deletions

70
.drone.yml Normal file
View File

@ -0,0 +1,70 @@
kind: pipeline
name: build
steps:
- name: gofmt
image: golang:1.22.1
commands:
- gofmt -l -s .
when:
event:
- push
- name: vuln-check
image: golang:1.22.1
commands:
- go install golang.org/x/vuln/cmd/govulncheck@latest
- govulncheck ./...
when:
event:
- push
- name: docker
image: thegeeklab/drone-docker-buildx
privileged: true
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/tom/sb-server-monitor
tags:
- latest
- ${DRONE_BUILD_NUMBER}
platforms:
- linux/arm64
- linux/amd64
when:
branch:
- main
event:
- push
- name: docker-build
image: thegeeklab/drone-docker-buildx
privileged: true
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/tom/sb-server-monitor
tags:
- latest
- ${DRONE_BUILD_NUMBER}
platforms:
- linux/arm64
- linux/amd64
dry_run: true
when:
branch:
exclude:
- main
event:
- push
volumes:
- name: deployment-repo
temp: {}
when:
event:
exclude:
- pull_request

View File

@ -0,0 +1,67 @@
package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type application struct {
server *http.Server
interval time.Duration
listenAddress string
}
func (app *application) webListener() {
log.Printf("listening on %s", app.listenAddress)
http.Handle("/", http.RedirectHandler("/metrics", http.StatusPermanentRedirect))
http.Handle("/metrics", promhttp.Handler())
if err := app.server.ListenAndServe(); err != nil {
log.Println(err)
}
}
var app = application{}
func init() {
flag.DurationVar(&app.interval, "interval", 5*time.Minute, "refresh interval for metrics (in seconds)")
flag.StringVar(&app.listenAddress, "web.listen-address", ":9192", "address to use for the metrics server")
app.server = &http.Server{
Handler: nil,
Addr: app.listenAddress,
}
}
func main() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGTERM)
signal.Notify(signals, syscall.SIGINT)
ctx, cancelWorkers := context.WithCancel(context.Background())
done := make(chan interface{})
go app.webListener()
log.Printf("start worker")
go app.refreshWorker(ctx, done)
<-signals
cancelWorkers()
if err := app.server.Shutdown(context.Background()); err != nil {
log.Println(err)
}
log.Println("sb-server-monitor exporter stopped")
}

View File

@ -0,0 +1,86 @@
package main
import "github.com/prometheus/client_golang/prometheus"
const (
namespace = "sb_server_monitor"
MetricLabelServerID = "id"
MetricLabelServerName = "name"
MetricLabelServerCategory = "category"
MetricLabelServerCPU = "cpu"
MetricLabelServerDrives = "drives"
MetricLabelServerMemory = "memory"
MetricLabelServerECC = "ecc"
MetricLabelServerHighIO = "highio"
MetricLabelServerDatacenter = "datacenter"
MetricLabelServerDescription = "description"
MetricLabelServerDist = "dist"
MetricLabelServerInformation = "information"
MetricLabelServerPrice = "price"
MetricLabelServerFixedPrice = "fixed_price"
MetricLabelServerNextReduce = "next_reduce"
MetricLabelServerNextReduceTimestamp = "next_reduce_timestamp"
MetricLabelServerGeneralDrives = "general_drives"
MetricLabelServerHDDs = "hdds"
MetricLabelServerSATAs = "sata"
MetricLabelServerNVMes = "nvme"
MetricLabelServerSetupPrice = "setup_price"
MetricLabelServerSpecials = "specials"
MetricLabelServerBandwith = "bandwith"
MetricLabelServerTraffic = "traffic"
)
var (
metricServerCount = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: "server",
Name: "current_available",
Help: "total number of all current available servers",
},
[]string{
MetricLabelServerCategory,
},
)
metricServers = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: namespace,
Name: "servers",
Help: "list all servers with some useful information as labels",
},
[]string{
MetricLabelServerID,
MetricLabelServerName,
MetricLabelServerCategory,
MetricLabelServerCPU,
MetricLabelServerDrives,
MetricLabelServerMemory,
MetricLabelServerECC,
MetricLabelServerHighIO,
MetricLabelServerDatacenter,
MetricLabelServerDescription,
MetricLabelServerDist,
MetricLabelServerInformation,
MetricLabelServerPrice,
MetricLabelServerFixedPrice,
MetricLabelServerNextReduce,
MetricLabelServerNextReduceTimestamp,
MetricLabelServerGeneralDrives,
MetricLabelServerHDDs,
MetricLabelServerSATAs,
MetricLabelServerNVMes,
MetricLabelServerSetupPrice,
MetricLabelServerSpecials,
MetricLabelServerBandwith,
MetricLabelServerTraffic,
},
)
)
func init() {
prometheus.MustRegister(
metricServerCount,
metricServers,
)
}

View File

@ -0,0 +1,82 @@
package main
import (
"context"
"fmt"
"log"
"time"
"git.ar21.de/yolokube/sb-server-monitor/hrobot"
"github.com/prometheus/client_golang/prometheus"
)
func (app *application) refreshWorker(ctx context.Context, done chan<- interface{}) {
var sleepCounter int
client := hrobot.NewClient()
for {
select {
case <-ctx.Done():
done <- nil
return
default:
if sleepCounter/int(app.interval.Seconds()) == 0 {
break
}
log.Println("Fetch servers")
sleepCounter = 0
servers, err := client.SbServer.List(context.Background())
if err != nil {
log.Println(err)
break
}
log.Printf("found %d servers", len(servers))
assignParametersToMetricServers(servers)
assignParametersToMetricServerCount(servers[0].Category, len(servers))
}
sleepCounter++
time.Sleep(time.Second)
}
}
func assignParametersToMetricServerCount(category string, value int) {
metricServerCount.With(
prometheus.Labels{
MetricLabelServerCategory: category,
},
).Set(float64(value))
}
func assignParametersToMetricServers(servers []*hrobot.SbServer) {
for _, server := range servers {
metricServers.With(
prometheus.Labels{
MetricLabelServerID: fmt.Sprint(server.ID),
MetricLabelServerName: server.Name,
MetricLabelServerCategory: server.Category,
MetricLabelServerCPU: server.Hardware.CPU,
MetricLabelServerDrives: fmt.Sprint(server.Hardware.Drives),
MetricLabelServerMemory: fmt.Sprint(server.Hardware.Memory),
MetricLabelServerECC: fmt.Sprint(server.Hardware.ECC),
MetricLabelServerHighIO: fmt.Sprint(server.Hardware.HighIO),
MetricLabelServerDatacenter: server.Datacenter,
MetricLabelServerDescription: fmt.Sprint(server.Description),
MetricLabelServerDist: fmt.Sprint(server.Dist),
MetricLabelServerInformation: fmt.Sprint(server.Information),
MetricLabelServerPrice: fmt.Sprint(server.PriceInformation.Price),
MetricLabelServerFixedPrice: fmt.Sprint(server.PriceInformation.FixedPrice),
MetricLabelServerNextReduce: fmt.Sprint(server.PriceInformation.NextReduce),
MetricLabelServerNextReduceTimestamp: fmt.Sprint(server.PriceInformation.NextReduceTimestamp),
MetricLabelServerGeneralDrives: fmt.Sprint(server.DiskData.General),
MetricLabelServerHDDs: fmt.Sprint(server.DiskData.HDD),
MetricLabelServerSATAs: fmt.Sprint(server.DiskData.SATA),
MetricLabelServerNVMes: fmt.Sprint(server.DiskData.NVMe),
MetricLabelServerSetupPrice: fmt.Sprint(server.PriceInformation.SetupPrice),
MetricLabelServerSpecials: fmt.Sprint(server.Specials),
MetricLabelServerBandwith: fmt.Sprint(server.Network.Bandwidth),
MetricLabelServerTraffic: server.Network.Traffic,
},
).Set(float64(server.PriceInformation.Price))
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module git.ar21.de/yolokube/sb-server-monitor
go 1.22.1
require github.com/prometheus/client_golang v1.19.0
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.49.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
golang.org/x/sys v0.18.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

20
go.sum Normal file
View File

@ -0,0 +1,20 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI=
github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=

124
hrobot/client.go Normal file
View File

@ -0,0 +1,124 @@
package hrobot
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)
const Endpoint = "https://www.hetzner.com/_resources/app/jsondata/live_data_sb.json"
const UserAgent = "hrobot-go/" + Version
type Client struct {
endpoint string
httpClient *http.Client
applicationName string
applicationVersion string
userAgent string
SbServer SbServerClient
}
type ClientOption func(*Client)
func WithApplication(name, version string) ClientOption {
return func(client *Client) {
client.applicationName = name
client.applicationVersion = version
}
}
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) {
client.httpClient = httpClient
}
}
func NewClient(options ...ClientOption) *Client {
client := &Client{
endpoint: Endpoint,
httpClient: &http.Client{},
}
for _, option := range options {
option(client)
}
client.buildUserAgent()
client.SbServer = SbServerClient{client: client}
return client
}
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
url := c.endpoint + path
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req = req.WithContext(ctx)
return req, nil
}
func (c *Client) Do(r *http.Request, v interface{}) (*http.Response, error) {
var body []byte
var err error
if r.ContentLength > 0 {
body, err = io.ReadAll(r.Body)
r.Body.Close()
if err != nil {
return nil, err
}
}
if r.ContentLength > 0 {
r.Body = io.NopCloser(bytes.NewReader(body))
}
resp, err := c.httpClient.Do(r)
if err != nil {
return nil, err
}
body, err = io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return resp, err
}
resp.Body = io.NopCloser(bytes.NewReader(body))
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
return resp, fmt.Errorf("hrobot: server responded with status %s", resp.Status)
}
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, bytes.NewReader(body))
} else {
err = json.Unmarshal(body, v)
}
}
return resp, err
}
func (c *Client) buildUserAgent() {
switch {
case c.applicationName != "" && c.applicationVersion != "":
c.userAgent = c.applicationName + "/" + c.applicationVersion + " " + UserAgent
case c.applicationName != "" && c.applicationVersion == "":
c.userAgent = c.applicationName + " " + UserAgent
default:
c.userAgent = UserAgent
}
}

3
hrobot/hrobot.go Normal file
View File

@ -0,0 +1,3 @@
package hrobot
const Version = "0.1.0"

75
hrobot/sbserver.go Normal file
View File

@ -0,0 +1,75 @@
package hrobot
import (
"context"
"time"
"git.ar21.de/yolokube/sb-server-monitor/hrobot/schema"
)
type SbServer struct {
ID int64
Name string
Key int64
Network struct {
Bandwidth int64
Traffic string
}
CatID int64
Category string
Hardware struct {
CPU string
CPUCount int8
Drives []string
DrivesCount int8
DrivesHr []string
DrivesSize int64
Memory []string
MemorySize int64
ECC bool
HighIO bool
}
Datacenter string
DatacenterHr string
Description []string
Dist []string
Information []string
PriceInformation struct {
FixedPrice bool
NextReduce int64
NextReduceHr bool
NextReduceTimestamp time.Time
Price int64
SetupPrice int64
}
DiskData struct {
General []int
HDD []int
NVMe []int
SATA []int
}
Specials []string
}
type SbServerClient struct {
client *Client
}
func (c *SbServerClient) List(ctx context.Context) ([]*SbServer, error) {
req, err := c.client.NewRequest(ctx, "GET", "", nil)
if err != nil {
return nil, err
}
var body schema.SbServerList
_, err = c.client.Do(req, &body)
if err != nil {
return nil, err
}
var servers []*SbServer
for _, s := range body.SbServers {
servers = append(servers, SbServerFromSchema(s))
}
return servers, nil
}

13
hrobot/schema.go Normal file
View File

@ -0,0 +1,13 @@
package hrobot
import "git.ar21.de/yolokube/sb-server-monitor/hrobot/schema"
var c converter
func init() {
c = &converterImpl{}
}
func SbServerFromSchema(s schema.SbServer) *SbServer {
return c.SbServerFromSchema(s)
}

44
hrobot/schema/sbserver.go Normal file
View File

@ -0,0 +1,44 @@
package schema
type SbServer struct {
ID int64 `json:"id"`
Name string `json:"name"`
Bandwidth int64 `json:"bandwidth"`
CatID int64 `json:"cat_id"`
Category string `json:"category"`
CPU string `json:"cpu"`
CPUCount int8 `json:"cpu_count"`
Datacenter string `json:"datacenter"`
DatacenterHr string `json:"datacenter_hr"`
Description []string `json:"description"`
Dist []string `json:"dist"`
FixedPrice bool `json:"fixed_price"`
Drive []string `json:"hdd_arr"`
DriveCount int8 `json:"hdd_count"`
DriveHr []string `json:"hdd_hr"`
DriveSize int64 `json:"hdd_size"`
Information []string `json:"information"`
ECC bool `json:"is_ecc"`
HighIO bool `json:"is_highio"`
Key int64 `json:"key"`
NextReduce int64 `json:"next_reduce"`
NextReduceHr bool `json:"next_reduce_hr"`
NextReduceTimestamp int64 `json:"next_reduce_timestamp"`
Price int64 `json:"price"`
Ram []string `json:"ram"`
RamSize int64 `json:"ram_size"`
DiskData struct {
General []int `json:"general"`
HDD []int `json:"hdd"`
NVMe []int `json:"nvme"`
SATA []int `json:"sata"`
SetupPrice int64 `json:"setup_price"`
} `json:"serverDiskData"`
Specials []string `json:"specials"`
Traffic string `json:"traffic"`
}
type SbServerList struct {
SbServers []SbServer `json:"server"`
ServerCount int64 `json:"serverCount"`
}

7
hrobot/schema_gen.go Normal file
View File

@ -0,0 +1,7 @@
package hrobot
import "git.ar21.de/yolokube/sb-server-monitor/hrobot/schema"
type converter interface {
SbServerFromSchema(schema.SbServer) *SbServer
}

47
hrobot/zz_schema.go Normal file
View File

@ -0,0 +1,47 @@
package hrobot
import (
"time"
"git.ar21.de/yolokube/sb-server-monitor/hrobot/schema"
)
type converterImpl struct{}
func (c *converterImpl) SbServerFromSchema(source schema.SbServer) *SbServer {
var sbServer SbServer
sbServer.Category = source.Category
sbServer.CatID = source.CatID
sbServer.Datacenter = source.Datacenter
sbServer.DatacenterHr = source.DatacenterHr
sbServer.Description = source.Description
sbServer.DiskData.General = source.DiskData.General
sbServer.DiskData.HDD = source.DiskData.HDD
sbServer.DiskData.NVMe = source.DiskData.NVMe
sbServer.DiskData.SATA = source.DiskData.SATA
sbServer.Dist = source.Dist
sbServer.Hardware.CPU = source.CPU
sbServer.Hardware.CPUCount = source.CPUCount
sbServer.Hardware.Drives = source.Drive
sbServer.Hardware.DrivesCount = source.DriveCount
sbServer.Hardware.DrivesHr = source.DriveHr
sbServer.Hardware.DrivesSize = source.DriveSize
sbServer.Hardware.ECC = source.ECC
sbServer.Hardware.HighIO = source.HighIO
sbServer.Hardware.Memory = source.Ram
sbServer.Hardware.MemorySize = source.RamSize
sbServer.ID = source.ID
sbServer.Information = source.Information
sbServer.Key = source.Key
sbServer.Name = source.Name
sbServer.Network.Bandwidth = source.Bandwidth
sbServer.Network.Traffic = source.Traffic
sbServer.PriceInformation.FixedPrice = source.FixedPrice
sbServer.PriceInformation.NextReduce = source.NextReduce
sbServer.PriceInformation.NextReduceHr = source.NextReduceHr
sbServer.PriceInformation.NextReduceTimestamp = time.Unix(source.NextReduceTimestamp, 0)
sbServer.PriceInformation.Price = source.Price
sbServer.PriceInformation.SetupPrice = source.DiskData.SetupPrice
sbServer.Specials = source.Specials
return &sbServer
}