feature/tt/initial-implementation (#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:
30
internal/config.go
Normal file
30
internal/config.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/configor"
|
||||
)
|
||||
|
||||
type Config = struct {
|
||||
LogLevel string `default:"Info" env:"CANADA_KAKTUS_LOGLEVEL"`
|
||||
LabelSelector string `default:"kops.k8s.io/instance-role=Node" env:"CANADA_KAKTUS_LABELSELECTOR"`
|
||||
HcloudToken string `default:"" env:"CANADA_KAKTUS_HCLOUD_TOKEN"`
|
||||
}
|
||||
|
||||
func GenConfig() (cfg *Config, err error) {
|
||||
|
||||
cfg = &Config{}
|
||||
|
||||
err = configor.New(&configor.Config{
|
||||
ENVPrefix: "CANADA_KAKTUS",
|
||||
AutoReload: true,
|
||||
Silent: true,
|
||||
AutoReloadInterval: time.Minute}).Load(cfg, "config.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating Config: %s", err.Error())
|
||||
}
|
||||
return cfg, nil
|
||||
|
||||
}
|
22
internal/config_test.go
Normal file
22
internal/config_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var defaultConfig = Config{
|
||||
LogLevel: "Info",
|
||||
LabelSelector: "kops.k8s.io/instance-role=Node",
|
||||
HcloudToken: "",
|
||||
}
|
||||
|
||||
func TestConfigDefaults(t *testing.T) {
|
||||
cfg, err := GenConfig()
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(&defaultConfig, cfg) {
|
||||
t.Errorf("got %+v, want %+v", cfg, defaultConfig)
|
||||
}
|
||||
}
|
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()))
|
||||
}
|
||||
}
|
23
internal/health_test.go
Normal file
23
internal/health_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
go func() {
|
||||
StartHealthEndpoint()
|
||||
}()
|
||||
request, _ := http.NewRequest(http.MethodGet, "http://localhost:8080/health", strings.NewReader(""))
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Health endpoint did not start: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Bad response from health endpoint. Want: %d, got %d", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
}
|
40
internal/hetzner.go
Normal file
40
internal/hetzner.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetAllNodes(cfg *Config) ([]*hcloud.Server, error) {
|
||||
client := hcloud.NewClient(hcloud.WithToken(cfg.HcloudToken))
|
||||
servers, _, err := client.Server.List(context.TODO(), hcloud.ServerListOpts{
|
||||
ListOpts: hcloud.ListOpts{
|
||||
LabelSelector: cfg.LabelSelector,
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing Hetzner Nodes: %s", err.Error())
|
||||
}
|
||||
|
||||
if servers == nil {
|
||||
return nil, fmt.Errorf("no Nodes found with label selector: %s", cfg.LabelSelector)
|
||||
}
|
||||
return servers, nil
|
||||
|
||||
}
|
||||
|
||||
func GetAllIps(servers []*hcloud.Server) ([]string, error) {
|
||||
ips := make([]string, len(servers))
|
||||
for i, instance := range servers {
|
||||
if instance.PublicNet.IPv4.IP == nil {
|
||||
return []string{""}, fmt.Errorf("instance %s has no public Addresses", instance.Name)
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "GetAllIps",
|
||||
}).Info(fmt.Sprintf("Found IP: %s", instance.PrivateNet[0].IP.String()))
|
||||
ips[i] = instance.PublicNet.IPv4.IP.String()
|
||||
}
|
||||
return ips, nil
|
||||
}
|
30
internal/hetzner_test.go
Normal file
30
internal/hetzner_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hetznercloud/hcloud-go/hcloud"
|
||||
)
|
||||
|
||||
func TestGetAllIps(t *testing.T) {
|
||||
|
||||
servers := []*hcloud.Server{{
|
||||
Status: hcloud.ServerStatusRunning,
|
||||
Name: "Test",
|
||||
}}
|
||||
|
||||
servers = append(servers, &hcloud.Server{
|
||||
Status: hcloud.ServerStatusRunning,
|
||||
Name: "Test2",
|
||||
})
|
||||
|
||||
expectedError := "instance Test has no public Addresses"
|
||||
_, err := GetAllIps(servers)
|
||||
if err == nil {
|
||||
t.Error("GetAllIps did not error with missing data")
|
||||
}
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Wrong error message. want %s, got %s", expectedError, err.Error())
|
||||
}
|
||||
|
||||
}
|
111
internal/k8s.go
Normal file
111
internal/k8s.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var IP_POOL_TEMPLATE = `
|
||||
{
|
||||
"apiVersion": "cilium.io/v2alpha1",
|
||||
"kind": "CiliumLoadBalancerIPPool",
|
||||
"metadata": {
|
||||
"name": "{{ .Name }}",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"blocks": [
|
||||
{{- range $i, $ip := .IPs }}
|
||||
{{- if $i}},{{ end }}
|
||||
{
|
||||
"cidr": "{{ $ip }}"
|
||||
}
|
||||
{{- end }}
|
||||
],
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type CrdConfig struct {
|
||||
Name string
|
||||
IPs []string
|
||||
}
|
||||
|
||||
func RecreateIPPoolCrd(cfg *Config, name string, ips []string) error {
|
||||
|
||||
routeclient, err := createRestClient()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating REST Client: %v", err.Error())
|
||||
}
|
||||
|
||||
body, err := generateIpPool(name, ips)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating CRD: %v", err.Error())
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
|
||||
obj, _, err := decode([]byte(body), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deserialize CRD: %v", err.Error())
|
||||
}
|
||||
|
||||
res := routeclient.Post().
|
||||
Resource("routes").
|
||||
Body(&obj).
|
||||
Do(context.TODO())
|
||||
|
||||
var status int
|
||||
res.StatusCode(&status)
|
||||
|
||||
if status >= 200 && status <= 400 {
|
||||
return fmt.Errorf("failed to post CRD to kube api: %v", res.Error().Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createRestClient() (*rest.RESTClient, error) {
|
||||
k8s_config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create in cluster k8s config: %v", err)
|
||||
}
|
||||
|
||||
k8s_config.APIPath = "/apis"
|
||||
k8s_config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||
|
||||
routeclient, err := rest.RESTClientFor(k8s_config)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create k8s client: %v", err)
|
||||
}
|
||||
return routeclient, nil
|
||||
|
||||
}
|
||||
|
||||
func generateIpPool(name string, ips []string) (string, error) {
|
||||
config := CrdConfig{
|
||||
Name: name,
|
||||
IPs: ips,
|
||||
}
|
||||
tmpl, err := template.New("ippool").Parse(IP_POOL_TEMPLATE)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("errors in ippool template: %s", err.Error())
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, &config)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not render ippool template: %s", err.Error())
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
38
internal/k8s_test.go
Normal file
38
internal/k8s_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateIpPoolCRD(t *testing.T) {
|
||||
expected_ip_pool := `
|
||||
{
|
||||
"apiVersion": "cilium.io/v2alpha1",
|
||||
"kind": "CiliumLoadBalancerIPPool",
|
||||
"metadata": {
|
||||
"name": "covidnetes-pool",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"blocks": [
|
||||
{
|
||||
"cidr": "49.13.48.9/32"
|
||||
},
|
||||
{
|
||||
"cidr": "91.107.211.117/32"
|
||||
}
|
||||
],
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
`
|
||||
got, err := generateIpPool("covidnetes-pool", []string{"49.13.48.9/32", "91.107.211.117/32"})
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
if expected_ip_pool != got {
|
||||
t.Errorf("got %+v, want %+v", got, expected_ip_pool)
|
||||
}
|
||||
}
|
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/canada-kaktus/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