Reviewed-on: #1
Co-authored-by: Tobias Trabelsi <lerentis@uploadfilter24.eu>
Co-committed-by: Tobias Trabelsi <lerentis@uploadfilter24.eu>
This commit is contained in:
2023-10-15 19:50:59 +00:00
committed by lerentis
parent b2bdedd1de
commit b72c1d1edb
20 changed files with 735 additions and 0 deletions

34
internal/config.go Normal file
View File

@ -0,0 +1,34 @@
package internal
import (
"errors"
"fmt"
"time"
"github.com/jinzhu/configor"
)
type Config = struct {
LogLevel string `default:"Info" env:"METALLB_IP_FLOATER_LOGLEVEL"`
LabelSelector string `default:"kops.k8s.io/instance-role=Node" env:"METALLB_IP_FLOATER_LABELSELECTOR"`
Protocol string `default:"http" env:"METALLB_IP_FLOATER_PROTOCOL"`
HcloudToken string `default:"" env:"METALLB_IP_FLOATER_HCLOUD_TOKEN"`
FloatingIPName string `default:"" env:"METALLB_IP_FLOATER_FLOATING_IP_NAME"`
DryRun bool `default:"false" env:"METALLB_IP_FLOATER_DRY_RUN"`
}
func GenConfig() (cfg *Config, err error) {
cfg = &Config{}
err = configor.New(&configor.Config{
ENVPrefix: "METALLB_IP_FLOATER",
AutoReload: true,
Silent: true,
AutoReloadInterval: time.Minute}).Load(cfg, "config.json")
if err != nil {
return nil, errors.New(fmt.Sprintf("Error generating Config: %s", err.Error()))
}
return cfg, nil
}

31
internal/health.go Normal file
View File

@ -0,0 +1,31 @@
package internal
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
func StartHealthEndpoint() {
r := mux.NewRouter()
r.Use(mux.CORSMethodMiddleware(r))
r.HandleFunc("/health", send200).Methods(http.MethodGet)
err := http.ListenAndServe("0.0.0.0:8080", r)
if err != nil {
log.WithFields(log.Fields{
"Caller": "StartHealthEndpoint",
}).Error(fmt.Sprintf("Error creating health endpoint: %s", err.Error()))
}
}
func send200(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte{})
if err != nil {
log.WithFields(log.Fields{
"Caller": "send200",
}).Error(fmt.Sprintf("Error answering health endpoint: %s", err.Error()))
}
}

88
internal/hetzner.go Normal file
View File

@ -0,0 +1,88 @@
package internal
import (
"context"
"errors"
"fmt"
"net"
"github.com/hetznercloud/hcloud-go/hcloud"
log "github.com/sirupsen/logrus"
)
func GetSpeakableNodes(cfg *Config) ([]string, error) {
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
server, _, err := client.Server.List(context.TODO(), hcloud.ServerListOpts{
ListOpts: hcloud.ListOpts{
LabelSelector: cfg.LabelSelector,
}})
if err != nil {
return nil, errors.New(fmt.Sprintf("Error listing Hetzner Nodes: %s", err.Error()))
}
ips := make([]string, len(server))
if server != nil {
for i, instance := range server {
log.WithFields(log.Fields{
"Caller": "GetSpeakableNodes",
}).Info(fmt.Sprintf("Found IP: %s", instance.PrivateNet[0].IP.String()))
ips[i] = instance.PrivateNet[0].IP.String()
}
} else {
return nil, errors.New(fmt.Sprintf("No Nodes found with label selector: %s", cfg.LabelSelector))
}
return ips, nil
}
func GetNodeID(cfg *Config, ip string) (hcloud.Server, error) {
// get can only be done via name or id: https://pkg.go.dev/github.com/hetznercloud/hcloud-go/v2/hcloud#ServerClient.Get
// we can iterate over all nodes and select the one that matches the IP we know from speaker: https://pkg.go.dev/github.com/hetznercloud/hcloud-go/v2/hcloud#ServerClient.List
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
serverList, _, err := client.Server.List(context.TODO(), hcloud.ServerListOpts{
ListOpts: hcloud.ListOpts{
Page: 1,
PerPage: 100,
},
Status: []hcloud.ServerStatus{hcloud.ServerStatusRunning},
})
if err != nil {
return hcloud.Server{}, errors.New(fmt.Sprintf("Could not get Server List from Hetzner: %s", err.Error()))
}
for _, server := range serverList {
log.WithFields(log.Fields{
"Caller": "GetNodeID",
}).Info(fmt.Sprintf("Checking Node %d for announcing IP", server.ID))
if server.PublicNet.IPv4.IP.Equal(net.ParseIP(ip)) {
log.WithFields(log.Fields{
"Caller": "GetNodeID",
}).Info(fmt.Sprintf("Match on Node %d", server.ID))
return *server, nil
}
for _, network := range server.PrivateNet {
if network.IP.Equal(net.ParseIP(ip)) {
log.WithFields(log.Fields{
"Caller": "GetNodeID",
}).Info(fmt.Sprintf("Match on Node %d", server.ID))
return *server, nil
}
}
}
return hcloud.Server{}, errors.New(fmt.Sprintf("Could not find correct server"))
}
func AttachFloatingIpToNode(cfg *Config, server hcloud.Server) error {
// get floating ip by name. name can be set to config https://github.com/hetznercloud/hcloud-go/blob/v2.3.0/hcloud/floating_ip.go#L117
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
floatingIP, _, err := client.FloatingIP.GetByName(context.TODO(), cfg.FloatingIPName)
if err != nil {
return errors.New(fmt.Sprintf("Could not find Floating IP by name: %s", err.Error()))
}
_, _, err = client.FloatingIP.Assign(context.TODO(), floatingIP, &server)
if err != nil {
log.WithFields(log.Fields{
"Caller": "AttachFloatingIpToNode",
}).Error(fmt.Sprintf("Error response while attaching floating ip: %s", err.Error()))
}
return err
}

55
internal/metrics.go Normal file
View File

@ -0,0 +1,55 @@
package internal
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
log "github.com/sirupsen/logrus"
)
func ParseMetrics(metrics string) (map[string]*dto.MetricFamily, error) {
reader := strings.NewReader(metrics)
var parser expfmt.TextParser
mf, err := parser.TextToMetricFamilies(reader)
if err != nil {
return nil, errors.New(fmt.Sprintf("Error decoding metrics: %s", err.Error()))
}
return mf, nil
}
func GetMetrics(cfg *Config, endpoint string) (string, error) {
resp, err := http.Get(fmt.Sprintf("%s://%s", cfg.Protocol, endpoint))
if err != nil {
return "", errors.New(fmt.Sprintf("Error getting metrics: %s", err.Error()))
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
bodyString := string(bodyBytes)
return bodyString, nil
}
return "", errors.New(fmt.Sprintf("None ok return code from metrics endoint: %s", resp.Status))
}
func GetIpFromMetrics(metrics *dto.MetricFamily) (string, error) {
for _, metric := range metrics.Metric {
for _, label := range metric.Label {
if *label.Name == "ip" {
log.WithFields(log.Fields{
"Caller": "Main",
}).Info(fmt.Sprintf("Found IP Label: %s", *label.Value))
return *label.Value, nil
}
}
}
return "", errors.New("Could not find 'ip' in metrics labels")
}

28
internal/utils/logging.go Normal file
View File

@ -0,0 +1,28 @@
package utils
import (
"os"
"git.uploadfilter24.eu/covidnetes/metallb-ip-floater/internal"
log "github.com/sirupsen/logrus"
)
func ConfigureLogger(cfg *internal.Config) {
switch cfg.LogLevel {
case "Debug":
log.SetLevel(log.DebugLevel)
case "Info":
log.SetLevel(log.InfoLevel)
case "Warn":
log.SetLevel(log.WarnLevel)
case "Error":
log.SetLevel(log.ErrorLevel)
default:
log.SetLevel(log.InfoLevel)
log.Warnf("Home: invalid log level supplied: '%s'", cfg.LogLevel)
}
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
}