init (#1)
Reviewed-on: #1 Co-authored-by: Tobias Trabelsi <lerentis@uploadfilter24.eu> Co-committed-by: Tobias Trabelsi <lerentis@uploadfilter24.eu>
This commit is contained in:
34
internal/config.go
Normal file
34
internal/config.go
Normal 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
31
internal/health.go
Normal 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
88
internal/hetzner.go
Normal 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
55
internal/metrics.go
Normal 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
28
internal/utils/logging.go
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user