added serve-http option

This commit is contained in:
Mahmoud Rahbar Azad 2018-10-26 22:31:39 +02:00
parent c0539ade72
commit 4a726291a2
No known key found for this signature in database
GPG Key ID: 7DBBD39E2BFEB784
9 changed files with 313 additions and 96 deletions

View File

@ -1,13 +1,13 @@
The aim of my-bloody-hetzner-sb-notifier is a simple CLI to fetch the current Hetzner Serverbörse deals and filter them according to CLI parameters sorted by score.
The score is calculated from the amount of HDD space as well as RAM and CPU-Benchnmark for better comparability.
For each offer a score is calculated from the amount of HDD space as well as RAM and CPU-Benchnmark for better comparability.
The CLI interface looks like this:
````
Usage of hetzner-sb-notifier:
-alert-on-score int
set alert on score
-max-benchmark int
-min-cpu-benchmark int
set min benchmark
-max-cpu-benchmark int
set max benchmark (default 20000)
-max-hdd-count int
set max hdd count (default 15)
@ -17,8 +17,6 @@ Usage of hetzner-sb-notifier:
set max price (default 297)
-max-ram int
set max ram (default 256)
-min-benchmark int
set min benchmark
-min-hdd-count int
set min hdd count
-min-hdd-size int
@ -27,8 +25,14 @@ Usage of hetzner-sb-notifier:
set min price
-min-ram int
set min ram
-serve-http
set serve http
-serve-http-port int
set serve http port (default 8080)
````
## Http mode
## Example
./hetzner-sb-notifier --max-price 77 --min-ram 128 --min-hdd-count 2 --min-hdd-size 4096

View File

@ -1,51 +1,41 @@
package crawler
import (
"fmt"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
"os"
"sort"
"text/tabwriter"
)
type Crawler struct {
tabWriter *tabwriter.Writer
type Parameter struct {
MinPrice float64
MaxPrice float64
minPrice float64
maxPrice float64
MinRam int64
MaxRam int64
minRam int
maxRam int
MinHddSize int64
MaxHddSize int64
minHddSize int
maxHddSize int
MinHddCount int64
MaxHddCount int64
minHddCount int
maxHddCount int
minBenchmark int
maxBenchmark int
MinBenchmark int64
MaxBenchmark int64
}
func NewCrawler(minPrice float64, maxPrice float64, minRam int, maxRam int, minHddSize int, maxHddSize int, minHddCount int, maxHddCount int, minBenchmark int, maxBenchmark int) *Crawler {
type Crawler struct {
parameter Parameter
}
func NewCrawler(parameter Parameter) *Crawler {
crawler := &Crawler{
tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', tabwriter.Debug|tabwriter.AlignRight),
minPrice,
maxPrice,
minRam,
maxRam,
minHddSize,
maxHddSize,
minHddCount,
maxHddCount,
minBenchmark,
maxBenchmark,
parameter: parameter,
}
return crawler
}
func (c *Crawler) Filter(servers []hetzner.Server) []hetzner.Server {
func (c *Crawler) Filter(servers []hetzner.Server) hetzner.Deals {
var filteredServers []hetzner.Server
for _, server := range servers {
if !c.isFiltered(server) {
@ -56,26 +46,23 @@ func (c *Crawler) Filter(servers []hetzner.Server) []hetzner.Server {
sort.Slice(servers, func(i, j int) bool {
return servers[i].Score() > servers[j].Score()
})
return filteredServers
}
func (c *Crawler) Print(servers []hetzner.Server) {
fmt.Fprintf(c.tabWriter, "%s\n", servers[0].Header())
for _, server := range servers {
fmt.Fprintf(c.tabWriter, "%s\n", server.ToString())
deals := hetzner.Deals{
ResultStats: hetzner.FilterResultStats{OriginalCount: len(servers), FilteredCount: len(filteredServers)},
Servers: filteredServers,
}
c.tabWriter.Flush()
return deals
}
func (c *Crawler) isFiltered(server hetzner.Server) bool {
filtered := true
priceParsed := server.ParsePrice()
if server.CpuBenchmark >= c.minBenchmark && server.CpuBenchmark <= c.maxBenchmark &&
priceParsed >= c.minPrice && priceParsed <= c.maxPrice &&
server.Ram >= c.minRam && server.Ram <= c.maxRam &&
server.TotalHdd() >= c.minHddSize && server.TotalHdd() <= c.maxHddSize &&
server.HddCount >= c.minHddCount && server.HddCount <= c.maxHddCount {
if server.CpuBenchmark >= c.parameter.MinBenchmark && server.CpuBenchmark <= c.parameter.MaxBenchmark &&
priceParsed >= c.parameter.MinPrice && priceParsed <= c.parameter.MaxPrice &&
server.Ram >= c.parameter.MinRam && server.Ram <= c.parameter.MaxRam &&
server.TotalHdd() >= c.parameter.MinHddSize && server.TotalHdd() <= c.parameter.MaxHddSize &&
server.HddCount >= c.parameter.MinHddCount && server.HddCount <= c.parameter.MaxHddCount {
filtered = false
}

1
go.mod
View File

@ -4,6 +4,7 @@ require (
cloud.google.com/go v0.31.0
github.com/golang/protobuf v1.2.0
github.com/googleapis/gax-go v2.0.0+incompatible // indirect
github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7 // indirect
github.com/mitchellh/gox v0.4.0 // indirect
github.com/mitchellh/iochan v1.0.0 // indirect
go.opencensus.io v0.18.0 // indirect

View File

@ -6,6 +6,16 @@ import (
"strings"
)
type Deals struct {
ResultStats FilterResultStats
Servers []Server
}
type FilterResultStats struct {
OriginalCount int `json:"count"`
FilteredCount int `json:"filtered"`
}
type Offers struct {
Server []Server `json:"server"`
}
@ -26,13 +36,13 @@ type Server struct {
SetupPrice string `json:"setup_price"`
Cpu string `json:"cpu"`
CpuBenchmark int `json:"cpu_benchmark"`
CpuCount int `json:"cpu_count"`
Ram int `json:"ram"`
CpuBenchmark int64 `json:"cpu_benchmark"`
CpuCount int64 `json:"cpu_count"`
Ram int64 `json:"ram"`
RamHr string `json:"ram_hr"`
HddSize int `json:"hdd_size"`
HddSize int64 `json:"hdd_size"`
HddHr string `json:"hdd_hr"`
HddCount int `json:"hdd_count"`
HddCount int64 `json:"hdd_count"`
SpecialHdd string `json:"specialHdd"`
NextReduce int `json:"next_reduce"`
@ -43,7 +53,7 @@ type Server struct {
IsEcc bool `json:"is_ecc"`
}
func (s *Server) TotalHdd() int {
func (s *Server) TotalHdd() int64 {
return s.HddCount * s.HddSize
}

View File

@ -1,11 +0,0 @@
package instrumentation
type Instrumenter struct {
projectID string
}
func NewInstrumenter(projectID string) *Instrumenter {
return &Instrumenter{
projectID: projectID,
}
}

228
main.go
View File

@ -3,92 +3,256 @@ package main
import (
"flag"
"fmt"
"github.com/iancoleman/strcase"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/client"
c "github.com/mrahbar/my-bloody-hetzner-sb-notifier/crawler"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/crawler"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/writer"
"io"
"net/http"
"net/http/httputil"
"os"
"strconv"
)
const (
flagMinPrice = "min-price"
flagMaxPrice = "max-price"
flagMinRam = "min-ram"
flagMaxRam = "max-ram"
flagMinHddSize = "min-hdd-size"
flagMaxHddSize = "max-hdd-size"
flagMinHddCount = "min-hdd-count"
flagMaxHddCount = "max-hdd-count"
flagMinBenchmark = "min-cpu-benchmark"
flagMaxBenchmark = "max-cpu-benchmark"
flagOutput = "output"
flagServeHttpPort = "serve-http-port"
flagServeHttp = "serve-http"
)
var (
zeroIntValue = int64(0)
zeroFloatValue = float64(0)
defaultMaxPriceValue = float64(297)
defaultMaxRamValue = int64(256)
defaultMaxHddSizeValue = int64(6144)
defaultMaxHddCountValue = int64(15)
defaultMaxCpuBenchmarkValue = int64(20000)
minPrice = flag.Float64(
"min-price",
flagMinPrice,
0,
"set min price",
)
maxPrice = flag.Float64(
"max-price",
flagMaxPrice,
297,
"set max price",
)
minRam = flag.Int(
"min-ram",
minRam = flag.Int64(
flagMinRam,
0,
"set min ram",
)
maxRam = flag.Int(
"max-ram",
maxRam = flag.Int64(
flagMaxRam,
256,
"set max ram",
)
minHddSize = flag.Int(
"min-hdd-size",
minHddSize = flag.Int64(
flagMinHddSize,
0,
"set min hdd size",
)
maxHddSize = flag.Int(
"max-hdd-size",
maxHddSize = flag.Int64(
flagMaxHddSize,
6144,
"set max hdd size",
)
minHddCount = flag.Int(
"min-hdd-count",
minHddCount = flag.Int64(
flagMinHddCount,
0,
"set min hdd count",
)
maxHddCount = flag.Int(
"max-hdd-count",
maxHddCount = flag.Int64(
flagMaxHddCount,
15,
"set max hdd count",
)
minBenchmark = flag.Int(
"min-benchmark",
minBenchmark = flag.Int64(
flagMinBenchmark,
0,
"set min benchmark",
)
maxBenchmark = flag.Int(
"max-benchmark",
maxBenchmark = flag.Int64(
flagMaxBenchmark,
20000,
"set max benchmark",
)
alertOnScore = flag.Int(
"alert-on-score",
0,
"set alert on score",
serveHttp = flag.Bool(
flagServeHttp,
false,
"set serve http",
)
serveHttpPort = flag.Int(
flagServeHttpPort,
8080,
"set serve http port",
)
output = flag.String(
flagOutput,
"table",
"set output: one of table, json",
)
)
func main() {
flag.Parse()
if *serveHttp {
runHttp()
} else {
p := crawler.Parameter{
MinPrice: *minPrice,
MaxPrice: *maxPrice,
MinRam: *minRam,
MaxRam: *maxRam,
MinHddSize: *minHddSize,
MaxHddSize: *maxHddSize,
MinHddCount: *minHddCount,
MaxHddCount: *maxHddCount,
MinBenchmark: *minBenchmark,
MaxBenchmark: *maxBenchmark,
}
run(os.Stdout, p, *output)
}
}
func runHttp() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
b, err := httputil.DumpRequest(r, false)
if err == nil {
fmt.Printf("Got request: %s", string(b))
}
parameter := crawler.Parameter{
MinPrice: zeroFloatValue,
MaxPrice: defaultMaxPriceValue,
MinRam: zeroIntValue,
MaxRam: defaultMaxRamValue,
MinHddSize: zeroIntValue,
MaxHddSize: defaultMaxHddSizeValue,
MinHddCount: zeroIntValue,
MaxHddCount: defaultMaxHddCountValue,
MinBenchmark: zeroIntValue,
MaxBenchmark: defaultMaxCpuBenchmarkValue,
}
output := "table"
values := r.URL.Query()
for k, v := range values {
value := v[0]
switch k {
case strcase.ToLowerCamel(flagMinPrice):
parameter.MinPrice = parseFloatFlag(w, strcase.ToLowerCamel(flagMinPrice), value)
break
case strcase.ToLowerCamel(flagMaxPrice):
parameter.MaxPrice = parseFloatFlag(w, strcase.ToLowerCamel(flagMaxPrice), value)
break
case strcase.ToLowerCamel(flagMinRam):
parameter.MinRam = parseIntFlag(w, strcase.ToLowerCamel(flagMinRam), value)
break
case strcase.ToLowerCamel(flagMaxRam):
parameter.MaxRam = parseIntFlag(w, strcase.ToLowerCamel(flagMaxRam), value)
break
case strcase.ToLowerCamel(flagMinHddSize):
parameter.MinHddSize = parseIntFlag(w, strcase.ToLowerCamel(flagMinHddSize), value)
break
case strcase.ToLowerCamel(flagMaxHddSize):
parameter.MaxHddSize = parseIntFlag(w, strcase.ToLowerCamel(flagMaxHddSize), value)
break
case strcase.ToLowerCamel(flagMinHddCount):
parameter.MinHddCount = parseIntFlag(w, strcase.ToLowerCamel(flagMinHddCount), value)
break
case strcase.ToLowerCamel(flagMaxHddCount):
parameter.MaxHddCount = parseIntFlag(w, strcase.ToLowerCamel(flagMaxHddCount), value)
break
case strcase.ToLowerCamel(flagMinBenchmark):
parameter.MinBenchmark = parseIntFlag(w, strcase.ToLowerCamel(flagMinBenchmark), value)
break
case strcase.ToLowerCamel(flagMaxBenchmark):
parameter.MaxBenchmark = parseIntFlag(w, strcase.ToLowerCamel(flagMaxBenchmark), value)
break
case strcase.ToLowerCamel(flagOutput):
output = value
break
}
}
w.WriteHeader(http.StatusOK)
run(w, parameter, output)
})
address := fmt.Sprintf(":%d", *serveHttpPort)
fmt.Printf("Running http server on address %s\n", address)
fmt.Println(http.ListenAndServe(address, nil))
}
func parseFloatFlag(w http.ResponseWriter, flag string, value string) float64 {
parseValue, err := strconv.ParseFloat(value, 32)
if err == nil {
return parseValue
} else {
writeBadRequestResponseForQueryParameter(w, flag, err)
return zeroFloatValue
}
}
func parseIntFlag(w http.ResponseWriter, flag string, value string) int64 {
parseValue, err := strconv.ParseInt(value, 10, 32)
if err == nil {
return parseValue
} else {
writeBadRequestResponseForQueryParameter(w, flag, err)
return zeroIntValue
}
}
func writeBadRequestResponseForQueryParameter(w http.ResponseWriter, parameter string, err error) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("Error parsing query parameter %s: %s", parameter, err)))
}
func run(w io.Writer, parameter crawler.Parameter, output string) error {
offers := &hetzner.Offers{}
err := client.NewClient().DoRequest(hetzner.MakeURL(), offers)
if err != nil {
panic(fmt.Errorf("failed to get hetzner live data: %s", err))
return fmt.Errorf("failed to get hetzner live data: %s", err)
}
if len(offers.Server) > 0 {
crawler := c.NewCrawler(*minPrice, *maxPrice, *minRam, *maxRam, *minHddSize, *maxHddSize, *minHddCount, *maxHddCount, *minBenchmark, *maxBenchmark)
servers := crawler.Filter(offers.Server)
c := crawler.NewCrawler(parameter)
deals := c.Filter(offers.Server)
fmt.Printf("Got %d offers. Filtered offers: %d\n", len(offers.Server), len(servers))
crawler.Print(servers)
//notifier := n.NewInstrumenter(*notifierRecipient, *notifierSender, *notifierPassword)
switch output {
case "json":
writer.NewJsonWriter(w).Print(deals)
break
default:
case "table":
writer.NewTableWriter(w).Print(deals)
break
}
return nil
} else {
fmt.Println("Got no offers.")
return fmt.Errorf("got no offers.")
}
}

27
writer/json.go Normal file
View File

@ -0,0 +1,27 @@
package writer
import (
"encoding/json"
"fmt"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
"io"
)
type JsonWriter struct {
output io.Writer
}
func NewJsonWriter(output io.Writer)*JsonWriter {
return &JsonWriter{
output:output,
}
}
func (c *JsonWriter) Print(deals hetzner.Deals) {
b, err := json.Marshal(deals)
if err != nil {
fmt.Fprintf(c.output, "{\"error\": \"%s\"}", err)
return
}
fmt.Fprint(c.output, string(b))
}

28
writer/table.go Normal file
View File

@ -0,0 +1,28 @@
package writer
import (
"fmt"
"github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
"io"
"text/tabwriter"
)
type TableWriter struct {
tabWriter *tabwriter.Writer
}
func NewTableWriter(output io.Writer)*TableWriter {
return &TableWriter{
tabwriter.NewWriter(output, 0, 8, 2, ' ', tabwriter.Debug|tabwriter.AlignRight),
}
}
func (c *TableWriter) Print(deals hetzner.Deals) {
fmt.Fprintf(c.tabWriter,"Got %d offers. Filtered offers: %d\n", deals.ResultStats.OriginalCount, deals.ResultStats.FilteredCount)
fmt.Fprintf(c.tabWriter, "%s\n", deals.Servers[0].Header())
for _, server := range deals.Servers {
fmt.Fprintf(c.tabWriter, "%s\n", server.ToString())
}
c.tabWriter.Flush()
}

7
writer/writer.go Normal file
View File

@ -0,0 +1,7 @@
package writer
import "github.com/mrahbar/my-bloody-hetzner-sb-notifier/hetzner"
type Writer interface {
Print(deals hetzner.Deals)
}