feat(): initial untested implementation
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:"K8S_CILIUM_NODE_LABEL_LOGLEVEL"`
|
||||
CiliumLabel string `default:"cilium.uploadfilter24.eu/speaker" env:"K8S_CILIUM_NODE_LABEL_KEY"`
|
||||
DryRun bool `default:"false" env:"K8S_CILIUM_NODE_LABEL_DRY_RUN"`
|
||||
}
|
||||
|
||||
func GenConfig() (cfg *Config, err error) {
|
||||
|
||||
cfg = &Config{}
|
||||
|
||||
err = configor.New(&configor.Config{
|
||||
ENVPrefix: "K8S_CILIUM_NODE_LABEL",
|
||||
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",
|
||||
CiliumLabel: "cilium.uploadfilter24.eu/speaker",
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
137
internal/kube.go
Normal file
137
internal/kube.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/coordination/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func generateClient() (kubernetes.Clientset, error) {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "generateClient",
|
||||
}).Info("Generating k8s client")
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return kubernetes.Clientset{}, fmt.Errorf("Could not generate in cluster config: %s", err.Error())
|
||||
}
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return kubernetes.Clientset{}, fmt.Errorf("Could not generate clientset: %s", err.Error())
|
||||
}
|
||||
return *clientset, nil
|
||||
}
|
||||
|
||||
func LabelNode(nodeName string, leaseName string, cfg *Config) error {
|
||||
|
||||
client, err := generateClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not generate client: %s", err.Error())
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "LabelNode",
|
||||
}).Info(fmt.Sprintf("Trying to label node %s", nodeName))
|
||||
|
||||
node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not find Kubernetes node: %s", err.Error())
|
||||
}
|
||||
|
||||
if node.Labels == nil {
|
||||
node.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
node.Labels[cfg.CiliumLabel] = "true"
|
||||
node.Labels["cilium.uploadfilter24.eu/lease"] = leaseName
|
||||
|
||||
_, err = client.CoreV1().Nodes().Update(context.TODO(), node, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not update node label: %s", err.Error())
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "LabelNode",
|
||||
}).Info(fmt.Sprintf("Node %s labeled with %s=%s\n", nodeName, cfg.CiliumLabel, "true"))
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func RemoveLabelFromNode(nodeName string) error {
|
||||
client, err := generateClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not generate client: %s", err.Error())
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "LabelNode",
|
||||
}).Info(fmt.Sprintf("Removing Label from node %s", nodeName))
|
||||
|
||||
labelKey := "metallb-speaker"
|
||||
|
||||
node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
delete(node.Labels, labelKey)
|
||||
|
||||
_, err = client.CoreV1().Nodes().Update(context.TODO(), node, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
fmt.Printf("Label %s removed from node %s\n", labelKey, nodeName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCiliumL2Leases() ([]v1.Lease, error) {
|
||||
client, err := generateClient()
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"Caller": "GetCiliumL2Leases"}).Errorf("Could not generate client: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
leases, err := client.CoordinationV1().Leases("kube-system").List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"Caller": "GetCiliumL2Leases"}).Errorf("Could not list leases: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciliumLeases := []v1.Lease{}
|
||||
|
||||
for _, l := range leases.Items {
|
||||
if strings.HasPrefix(l.Name, "cilium-l2announce") {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "GetCiliumL2Leases",
|
||||
"Lease": l.Name,
|
||||
}).Info("Found Cilium L2 lease")
|
||||
// Pretty-print a couple of fields for debugging
|
||||
if l.Spec.HolderIdentity != nil {
|
||||
log.WithFields(log.Fields{"HolderIdentity": *l.Spec.HolderIdentity}).Debug("HolderIdentity")
|
||||
}
|
||||
ciliumLeases = append(ciliumLeases, l)
|
||||
}
|
||||
}
|
||||
return ciliumLeases, nil
|
||||
}
|
||||
|
||||
func GetNodeNameFromLease(lease v1.Lease) (string, error) {
|
||||
if lease.Spec.HolderIdentity == nil {
|
||||
return "", fmt.Errorf("Lease %s has no HolderIdentity", lease.Name)
|
||||
}
|
||||
holderIdentity := *lease.Spec.HolderIdentity
|
||||
parts := strings.Split(holderIdentity, "-")
|
||||
if len(parts) < 3 {
|
||||
return "", fmt.Errorf("Unexpected HolderIdentity format: %s", holderIdentity)
|
||||
}
|
||||
nodeName := strings.Join(parts[2:], "-")
|
||||
return nodeName, nil
|
||||
}
|
||||
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/k8s-cilium-node-label/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