Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f81479069
|
|||
d3682557b1
|
|||
60844be81b
|
|||
76fb779d08
|
|||
bafd97fbaf
|
|||
49252a5f7a
|
|||
091ab2eb2f
|
|||
cd1dca0d5e
|
|||
b43b31335e
|
|||
6fdb629cc9
|
|||
5e3ee60c91
|
|||
eacc8ac9f2
|
|||
79fd7ff3b7
|
|||
6083039648
|
|||
85a62d24f4
|
|||
6ffe638db1
|
@@ -40,10 +40,10 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: false
|
||||
push: true
|
||||
tags: |
|
||||
lerentis/canada-kaktus:${{ github.sha }}
|
||||
- name: Sign the published Docker image
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: cosign sign lerentis/canada-kaktus:${{ github.sha }}@${{ steps.build-and-push.outputs.digest }}
|
||||
# - name: Sign the published Docker image
|
||||
# env:
|
||||
# COSIGN_EXPERIMENTAL: "true"
|
||||
# run: cosign sign lerentis/canada-kaktus:${{ github.sha }}@${{ steps.build-and-push.outputs.digest }}
|
@@ -40,7 +40,7 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
lerentis/canada-kaktus:${{ github.event.release.tag_name }}
|
||||
- name: Sign the published Docker image
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: cosign sign lerentis/canada-kaktus:${{ github.event.release.tag_name }}@${{ steps.build-and-push.outputs.digest }}
|
||||
# - name: Sign the published Docker image
|
||||
# env:
|
||||
# COSIGN_EXPERIMENTAL: "true"
|
||||
# run: cosign sign lerentis/canada-kaktus:${{ github.event.release.tag_name }}@${{ steps.build-and-push.outputs.digest }}
|
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.24 as build
|
||||
FROM golang:1.24 AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.uploadfilter24.eu/covidnetes/canada-kaktus/internal"
|
||||
@@ -20,11 +21,13 @@ func main() {
|
||||
}).Fatal(fmt.Sprintf("Error generating Config: %s", err.Error()))
|
||||
}
|
||||
|
||||
hs := internal.NewHealthServer()
|
||||
|
||||
go func() {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "Main",
|
||||
}).Info("Starting Health Endpoint")
|
||||
internal.StartHealthEndpoint()
|
||||
hs.Start()
|
||||
}()
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
@@ -37,18 +40,21 @@ func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "Main",
|
||||
}).Error(fmt.Sprintf("Error getting all Nodes: %s", err.Error()))
|
||||
hs.SetHealthState(http.StatusServiceUnavailable)
|
||||
}
|
||||
ips, err := internal.GetAllIps(servers)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "Main",
|
||||
}).Error(fmt.Sprintf("Error getting all IPs: %s", err.Error()))
|
||||
hs.SetHealthState(http.StatusServiceUnavailable)
|
||||
}
|
||||
err = internal.RecreateIPPoolCrd(cfg, "covidnetes-pool", ips)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "Main",
|
||||
}).Error(fmt.Sprintf("Error recreating IP Pool CRD: %s", err.Error()))
|
||||
hs.SetHealthState(http.StatusServiceUnavailable)
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "Main",
|
||||
|
2
go.mod
2
go.mod
@@ -7,6 +7,7 @@ require (
|
||||
github.com/hetznercloud/hcloud-go v1.59.2
|
||||
github.com/jinzhu/configor v1.2.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
)
|
||||
|
||||
@@ -38,7 +39,6 @@ require (
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.34.1 // indirect
|
||||
k8s.io/apimachinery v0.34.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
|
@@ -3,29 +3,54 @@ package internal
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func StartHealthEndpoint() {
|
||||
type HealthServer struct {
|
||||
mu sync.RWMutex
|
||||
state int
|
||||
}
|
||||
|
||||
func NewHealthServer() *HealthServer {
|
||||
return &HealthServer{
|
||||
state: http.StatusOK,
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *HealthServer) SetHealthState(code int) {
|
||||
hs.mu.Lock()
|
||||
defer hs.mu.Unlock()
|
||||
hs.state = code
|
||||
}
|
||||
|
||||
func (hs *HealthServer) GetHealthState() int {
|
||||
hs.mu.RLock()
|
||||
defer hs.mu.RUnlock()
|
||||
return hs.state
|
||||
}
|
||||
|
||||
func (hs *HealthServer) Start() {
|
||||
r := mux.NewRouter()
|
||||
r.Use(mux.CORSMethodMiddleware(r))
|
||||
r.HandleFunc("/health", send200).Methods(http.MethodGet)
|
||||
r.HandleFunc("/health", hs.sendHealth).Methods(http.MethodGet)
|
||||
err := http.ListenAndServe("0.0.0.0:8080", r)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "StartHealthEndpoint",
|
||||
"Caller": "HealthServer.Start",
|
||||
}).Error(fmt.Sprintf("Error creating health endpoint: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func send200(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
func (hs *HealthServer) sendHealth(w http.ResponseWriter, r *http.Request) {
|
||||
code := hs.GetHealthState()
|
||||
w.WriteHeader(code)
|
||||
_, err := w.Write([]byte{})
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "send200",
|
||||
"Caller": "HealthServer.sendHealth",
|
||||
}).Error(fmt.Sprintf("Error answering health endpoint: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,9 @@ import (
|
||||
)
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
hs := NewHealthServer()
|
||||
go func() {
|
||||
StartHealthEndpoint()
|
||||
hs.Start()
|
||||
}()
|
||||
request, _ := http.NewRequest(http.MethodGet, "http://localhost:8080/health", strings.NewReader(""))
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
|
@@ -18,11 +18,17 @@ func GetAllNodes(cfg *Config) ([]*hcloud.Server, error) {
|
||||
return nil, fmt.Errorf("error listing Hetzner Nodes: %s", err.Error())
|
||||
}
|
||||
|
||||
if servers == nil {
|
||||
if len(servers) == 0 {
|
||||
return nil, fmt.Errorf("no Nodes found with label selector: %s", cfg.LabelSelector)
|
||||
}
|
||||
return servers, nil
|
||||
|
||||
for _, instance := range servers {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "GetAllNodes",
|
||||
}).Debugf("Found server: %s", instance.Name)
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func GetAllIps(servers []*hcloud.Server) ([]string, error) {
|
||||
@@ -33,7 +39,7 @@ func GetAllIps(servers []*hcloud.Server) ([]string, error) {
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "GetAllIps",
|
||||
}).Info(fmt.Sprintf("Found IP: %s", instance.PrivateNet[0].IP.String()))
|
||||
}).Debugf("Found IP: %s", instance.PublicNet.IPv4.IP.String())
|
||||
ips[i] = instance.PublicNet.IPv4.IP.String()
|
||||
}
|
||||
return ips, nil
|
||||
|
@@ -3,13 +3,21 @@ package internal
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var CILIUM_GROUP_VERSION = schema.GroupVersion{
|
||||
Group: "cilium.io",
|
||||
Version: "v2alpha1",
|
||||
}
|
||||
|
||||
var IP_POOL_TEMPLATE = `
|
||||
{
|
||||
"apiVersion": "cilium.io/v2alpha1",
|
||||
@@ -17,7 +25,8 @@ var IP_POOL_TEMPLATE = `
|
||||
"metadata": {
|
||||
"name": "{{ .Name }}",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool"
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool",
|
||||
"managed-by": "canada-kaktus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
@@ -25,7 +34,7 @@ var IP_POOL_TEMPLATE = `
|
||||
{{- range $i, $ip := .IPs }}
|
||||
{{- if $i}},{{ end }}
|
||||
{
|
||||
"cidr": "{{ $ip }}"
|
||||
"cidr": "{{ $ip }}/32"
|
||||
}
|
||||
{{- end }}
|
||||
],
|
||||
@@ -41,40 +50,68 @@ type CrdConfig struct {
|
||||
|
||||
func RecreateIPPoolCrd(cfg *Config, name string, ips []string) error {
|
||||
|
||||
routeclient, err := createRestClient()
|
||||
if len(ips) == 0 {
|
||||
return fmt.Errorf("no IPs provided to create IP Pool CRD")
|
||||
}
|
||||
|
||||
routeclient, err := createRestClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating REST Client: %v", err.Error())
|
||||
}
|
||||
|
||||
body, err := generateIpPool(name, ips)
|
||||
resourceVersion, err := getResourceVersion(routeclient, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting resourceVersion: %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)
|
||||
// Inject resourceVersion into the JSON
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(body), &obj); err != nil {
|
||||
return fmt.Errorf("could not unmarshal generated CRD: %v", err)
|
||||
}
|
||||
if meta, ok := obj["metadata"].(map[string]interface{}); ok {
|
||||
meta["resourceVersion"] = resourceVersion
|
||||
}
|
||||
finalBody, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deserialize CRD: %v", err.Error())
|
||||
return fmt.Errorf("could not marshal final CRD: %v", err)
|
||||
}
|
||||
|
||||
res := routeclient.Post().
|
||||
Resource("routes").
|
||||
Body(&obj).
|
||||
res := routeclient.Put().
|
||||
Resource("ciliumloadbalancerippools").
|
||||
Name(name).
|
||||
Body(finalBody).
|
||||
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())
|
||||
raw, rawErr := res.Raw()
|
||||
|
||||
if status < 200 || status >= 400 {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "RecreateIPPoolCrd",
|
||||
}).Warnf("Response from k8s api server: %s", string(raw))
|
||||
return fmt.Errorf("failed to post CRD to kube api: %v", res.Error())
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "RecreateIPPoolCrd",
|
||||
}).Debugf("Response from k8s api server: %s", string(raw))
|
||||
|
||||
if rawErr != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"Caller": "RecreateIPPoolCrd",
|
||||
}).Warnf("Could not get raw response from k8s api server: %v", rawErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createRestClient() (*rest.RESTClient, error) {
|
||||
k8s_config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
@@ -83,6 +120,7 @@ func createRestClient() (*rest.RESTClient, error) {
|
||||
|
||||
k8s_config.APIPath = "/apis"
|
||||
k8s_config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||
k8s_config.GroupVersion = &CILIUM_GROUP_VERSION
|
||||
|
||||
routeclient, err := rest.RESTClientFor(k8s_config)
|
||||
|
||||
@@ -109,3 +147,27 @@ func generateIpPool(name string, ips []string) (string, error) {
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func getResourceVersion(client *rest.RESTClient, name string) (string, error) {
|
||||
res := client.Get().
|
||||
Resource("ciliumloadbalancerippools").
|
||||
Name(name).
|
||||
Do(context.TODO())
|
||||
raw, err := res.Raw()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not fetch CRD: %v", err)
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
return "", fmt.Errorf("could not unmarshal CRD: %v", err)
|
||||
}
|
||||
meta, ok := obj["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("metadata missing in CRD")
|
||||
}
|
||||
rv, ok := meta["resourceVersion"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("resourceVersion missing in metadata")
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
@@ -12,7 +12,8 @@ func TestGenerateIpPoolCRD(t *testing.T) {
|
||||
"metadata": {
|
||||
"name": "covidnetes-pool",
|
||||
"annotations": {
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool"
|
||||
"argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool",
|
||||
"managed-by": "canada-kaktus"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
@@ -28,7 +29,7 @@ func TestGenerateIpPoolCRD(t *testing.T) {
|
||||
}
|
||||
}
|
||||
`
|
||||
got, err := generateIpPool("covidnetes-pool", []string{"49.13.48.9/32", "91.107.211.117/32"})
|
||||
got, err := generateIpPool("covidnetes-pool", []string{"49.13.48.9", "91.107.211.117"})
|
||||
if err != nil {
|
||||
t.Errorf("%s", err.Error())
|
||||
}
|
||||
|
Reference in New Issue
Block a user