mirror of
https://github.com/jangraefen/hcloud-pricing-exporter
synced 2024-11-10 05:54:15 +00:00
Add support for primary IPs
This commit is contained in:
parent
5d61524891
commit
05dc439f7f
3 changed files with 225 additions and 168 deletions
|
@ -1,96 +1,115 @@
|
|||
package fetcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
// PriceProvider provides easy access to current HCloud prices.
|
||||
type PriceProvider struct {
|
||||
Client *hcloud.Client
|
||||
pricing *hcloud.Pricing
|
||||
pricingLock sync.RWMutex
|
||||
}
|
||||
|
||||
// FloatingIP returns the current price for a floating IP per month.
|
||||
func (provider *PriceProvider) FloatingIP(ipType hcloud.FloatingIPType, location string) float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
for _, byType := range provider.getPricing().FloatingIPs {
|
||||
if byType.Type == ipType {
|
||||
for _, pricing := range byType.Pricings {
|
||||
if pricing.Location.Name == location {
|
||||
return parsePrice(pricing.Monthly.Gross)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the pricing can not be determined by the type and location, we fall back to the old pricing
|
||||
return parsePrice(provider.getPricing().FloatingIP.Monthly.Gross)
|
||||
}
|
||||
|
||||
// Image returns the current price for an image per GB per month.
|
||||
func (provider *PriceProvider) Image() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Image.PerGBMonth.Gross)
|
||||
}
|
||||
|
||||
// Traffic returns the current price for a TB of extra traffic per month.
|
||||
func (provider *PriceProvider) Traffic() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Traffic.PerTB.Gross)
|
||||
}
|
||||
|
||||
// ServerBackup returns the percentage of base price increase for server backups per month.
|
||||
func (provider *PriceProvider) ServerBackup() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().ServerBackup.Percentage)
|
||||
}
|
||||
|
||||
// Volume returns the current price for a volume per GB per month.
|
||||
func (provider *PriceProvider) Volume() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Volume.PerGBMonthly.Gross)
|
||||
}
|
||||
|
||||
// Sync forces the provider to re-fetch prices from the HCloud API.
|
||||
func (provider *PriceProvider) Sync() {
|
||||
provider.pricingLock.Lock()
|
||||
defer provider.pricingLock.Unlock()
|
||||
|
||||
provider.pricing = nil
|
||||
}
|
||||
|
||||
func (provider *PriceProvider) getPricing() *hcloud.Pricing {
|
||||
if provider.pricing == nil {
|
||||
pricing, _, err := provider.Client.Pricing.Get(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider.pricing = &pricing
|
||||
}
|
||||
|
||||
return provider.pricing
|
||||
}
|
||||
|
||||
func parsePrice(rawPrice string) float64 {
|
||||
if price, err := strconv.ParseFloat(rawPrice, 32); err == nil {
|
||||
return price
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
package fetcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
// PriceProvider provides easy access to current HCloud prices.
|
||||
type PriceProvider struct {
|
||||
Client *hcloud.Client
|
||||
pricing *hcloud.Pricing
|
||||
pricingLock sync.RWMutex
|
||||
}
|
||||
|
||||
// FloatingIP returns the current price for a floating IP per month.
|
||||
func (provider *PriceProvider) FloatingIP(ipType hcloud.FloatingIPType, location string) float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
for _, byType := range provider.getPricing().FloatingIPs {
|
||||
if byType.Type == ipType {
|
||||
for _, pricing := range byType.Pricings {
|
||||
if pricing.Location.Name == location {
|
||||
return parsePrice(pricing.Monthly.Gross)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the pricing can not be determined by the type and location, we fall back to the old pricing
|
||||
return parsePrice(provider.getPricing().FloatingIP.Monthly.Gross)
|
||||
}
|
||||
|
||||
// PrimaryIP returns the current price for a primary IP per hour and month.
|
||||
func (provider *PriceProvider) PrimaryIP(ipType hcloud.PrimaryIPType, datacenter string) (hourly, monthly float64, err error) {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
for _, byType := range provider.getPricing().PrimaryIPs {
|
||||
if byType.Type == string(ipType) {
|
||||
for _, pricing := range byType.Pricings {
|
||||
if pricing.Datacenter == datacenter {
|
||||
return parsePrice(pricing.Hourly.Gross), parsePrice(pricing.Monthly.Gross), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, 0, fmt.Errorf("no primary IP pricing found for datacenter %s", datacenter)
|
||||
}
|
||||
|
||||
// Image returns the current price for an image per GB per month.
|
||||
func (provider *PriceProvider) Image() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Image.PerGBMonth.Gross)
|
||||
}
|
||||
|
||||
// Traffic returns the current price for a TB of extra traffic per month.
|
||||
func (provider *PriceProvider) Traffic() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Traffic.PerTB.Gross)
|
||||
}
|
||||
|
||||
// ServerBackup returns the percentage of base price increase for server backups per month.
|
||||
func (provider *PriceProvider) ServerBackup() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().ServerBackup.Percentage)
|
||||
}
|
||||
|
||||
// Volume returns the current price for a volume per GB per month.
|
||||
func (provider *PriceProvider) Volume() float64 {
|
||||
provider.pricingLock.RLock()
|
||||
defer provider.pricingLock.RUnlock()
|
||||
|
||||
return parsePrice(provider.getPricing().Volume.PerGBMonthly.Gross)
|
||||
}
|
||||
|
||||
// Sync forces the provider to re-fetch prices from the HCloud API.
|
||||
func (provider *PriceProvider) Sync() {
|
||||
provider.pricingLock.Lock()
|
||||
defer provider.pricingLock.Unlock()
|
||||
|
||||
provider.pricing = nil
|
||||
}
|
||||
|
||||
func (provider *PriceProvider) getPricing() *hcloud.Pricing {
|
||||
if provider.pricing == nil {
|
||||
pricing, _, err := provider.Client.Pricing.Get(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider.pricing = &pricing
|
||||
}
|
||||
|
||||
return provider.pricing
|
||||
}
|
||||
|
||||
func parsePrice(rawPrice string) float64 {
|
||||
if price, err := strconv.ParseFloat(rawPrice, 32); err == nil {
|
||||
return price
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
37
fetcher/primaryip.go
Normal file
37
fetcher/primaryip.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package fetcher
|
||||
|
||||
import (
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
var _ Fetcher = &floatingIP{}
|
||||
|
||||
// NewPrimaryIP creates a new fetcher that will collect pricing information on primary IPs.
|
||||
func NewPrimaryIP(pricing *PriceProvider) Fetcher {
|
||||
return &floatingIP{newBase(pricing, "floatingip", "location")}
|
||||
}
|
||||
|
||||
type primaryIP struct {
|
||||
*baseFetcher
|
||||
}
|
||||
|
||||
func (primaryIP primaryIP) Run(client *hcloud.Client) error {
|
||||
primaryIPs, _, err := client.PrimaryIP.List(ctx, hcloud.PrimaryIPListOpts{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range primaryIPs {
|
||||
datacenter := p.Datacenter
|
||||
|
||||
hourlyPrice, monthlyPrice, err := primaryIP.pricing.PrimaryIP(p.Type, datacenter.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
primaryIP.hourly.WithLabelValues(p.Name, datacenter.Name).Set(hourlyPrice)
|
||||
primaryIP.monthly.WithLabelValues(p.Name, datacenter.Name).Set(monthlyPrice)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
145
main.go
145
main.go
|
@ -1,72 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
"github.com/jangraefen/hcloud-pricing-exporter/fetcher"
|
||||
"github.com/jtaczanowski/go-scheduler"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPort = 8080
|
||||
defaultFetchInterval = 1 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
hcloudAPIToken string
|
||||
port uint
|
||||
fetchInterval time.Duration
|
||||
)
|
||||
|
||||
func handleFlags() {
|
||||
flag.StringVar(&hcloudAPIToken, "hcloud-token", "", "the token to authenticate against the HCloud API")
|
||||
flag.UintVar(&port, "port", defaultPort, "the port that the exporter exposes its data on")
|
||||
flag.DurationVar(&fetchInterval, "fetch-interval", defaultFetchInterval, "the interval between data fetching cycles")
|
||||
flag.Parse()
|
||||
|
||||
if hcloudAPIToken == "" {
|
||||
if envHCloudAPIToken, present := os.LookupEnv("HCLOUD_TOKEN"); present {
|
||||
hcloudAPIToken = envHCloudAPIToken
|
||||
}
|
||||
}
|
||||
if hcloudAPIToken == "" {
|
||||
panic(fmt.Errorf("no API token for HCloud specified, but required"))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
handleFlags()
|
||||
|
||||
client := hcloud.NewClient(hcloud.WithToken(hcloudAPIToken))
|
||||
priceRepository := &fetcher.PriceProvider{Client: client}
|
||||
|
||||
fetchers := fetcher.Fetchers{
|
||||
fetcher.NewFloatingIP(priceRepository),
|
||||
fetcher.NewLoadbalancer(priceRepository),
|
||||
fetcher.NewLoadbalancerTraffic(priceRepository),
|
||||
fetcher.NewServer(priceRepository),
|
||||
fetcher.NewServerBackup(priceRepository),
|
||||
fetcher.NewServerTraffic(priceRepository),
|
||||
fetcher.NewSnapshot(priceRepository),
|
||||
fetcher.NewVolume(priceRepository),
|
||||
}
|
||||
|
||||
fetchers.MustRun(client)
|
||||
scheduler.RunTaskAtInterval(func() { fetchers.MustRun(client) }, fetchInterval, 0)
|
||||
scheduler.RunTaskAtInterval(priceRepository.Sync, 10*fetchInterval, 10*fetchInterval)
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
fetchers.RegisterCollectors(registry)
|
||||
|
||||
http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
"github.com/jangraefen/hcloud-pricing-exporter/fetcher"
|
||||
"github.com/jtaczanowski/go-scheduler"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPort = 8080
|
||||
defaultFetchInterval = 1 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
hcloudAPIToken string
|
||||
port uint
|
||||
fetchInterval time.Duration
|
||||
)
|
||||
|
||||
func handleFlags() {
|
||||
flag.StringVar(&hcloudAPIToken, "hcloud-token", "", "the token to authenticate against the HCloud API")
|
||||
flag.UintVar(&port, "port", defaultPort, "the port that the exporter exposes its data on")
|
||||
flag.DurationVar(&fetchInterval, "fetch-interval", defaultFetchInterval, "the interval between data fetching cycles")
|
||||
flag.Parse()
|
||||
|
||||
if hcloudAPIToken == "" {
|
||||
if envHCloudAPIToken, present := os.LookupEnv("HCLOUD_TOKEN"); present {
|
||||
hcloudAPIToken = envHCloudAPIToken
|
||||
}
|
||||
}
|
||||
if hcloudAPIToken == "" {
|
||||
panic(fmt.Errorf("no API token for HCloud specified, but required"))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
handleFlags()
|
||||
|
||||
client := hcloud.NewClient(hcloud.WithToken(hcloudAPIToken))
|
||||
priceRepository := &fetcher.PriceProvider{Client: client}
|
||||
|
||||
fetchers := fetcher.Fetchers{
|
||||
fetcher.NewFloatingIP(priceRepository),
|
||||
fetcher.NewPrimaryIP(priceRepository),
|
||||
fetcher.NewLoadbalancer(priceRepository),
|
||||
fetcher.NewLoadbalancerTraffic(priceRepository),
|
||||
fetcher.NewServer(priceRepository),
|
||||
fetcher.NewServerBackup(priceRepository),
|
||||
fetcher.NewServerTraffic(priceRepository),
|
||||
fetcher.NewSnapshot(priceRepository),
|
||||
fetcher.NewVolume(priceRepository),
|
||||
}
|
||||
|
||||
fetchers.MustRun(client)
|
||||
scheduler.RunTaskAtInterval(func() { fetchers.MustRun(client) }, fetchInterval, 0)
|
||||
scheduler.RunTaskAtInterval(priceRepository.Sync, 10*fetchInterval, 10*fetchInterval)
|
||||
|
||||
registry := prometheus.NewRegistry()
|
||||
fetchers.RegisterCollectors(registry)
|
||||
|
||||
http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue