diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml new file mode 100644 index 0000000..68844b3 --- /dev/null +++ b/.gitea/workflows/main.yaml @@ -0,0 +1,79 @@ +name: Build and Test +run-name: Reference Branch Build 🚀 +on: + push: + branches: + - main + +jobs: + Test: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v5 + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: '1.25.x' + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.1 + - name: Test with the Go CLI + run: go test ./... -v + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: ./... + Build_Image_amd64: + runs-on: ubuntu-latest-amd64 + steps: + - name: Check out repository code + uses: actions/checkout@v5 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: remote + endpoint: tcp://buildkit-service.buildkit-service.svc:1234 + - name: Log in to Gitea OCI + uses: docker/login-action@v2 + with: + registry: git.uploadfilter24.eu + username: ${{ vars.GA_USER }} + password: ${{ secrets.GA_PAT }} + - name: Build and Push the Docker Image + uses: docker/build-push-action@v6 + id: build-and-push + with: + platforms: linux/amd64 + context: . + file: ./Dockerfile + push: true + tags: | + git.uploadfilter24.eu/covidnetes/${{ github.event.repository.name }}:${{ github.sha }} + Build_Image_arm64: + runs-on: ubuntu-latest-arm64 + steps: + - name: Check out repository code + uses: actions/checkout@v5 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: remote + endpoint: tcp://buildkit-service.buildkit-service.svc:1234 + - name: Log in to Gitea OCI + uses: docker/login-action@v2 + with: + registry: git.uploadfilter24.eu + username: ${{ vars.GA_USER }} + password: ${{ secrets.GA_PAT }} + - name: Build and Push the Docker Image + uses: docker/build-push-action@v6 + id: build-and-push + with: + platforms: linux/arm64 + context: . + file: ./Dockerfile + push: true + tags: | + git.uploadfilter24.eu/covidnetes/${{ github.event.repository.name }}:${{ github.sha }} \ No newline at end of file diff --git a/README.md b/README.md index f8fe3e5..7b3bea5 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,6 @@ roleRef: go test ./... ``` -## Next steps / TODOs - -- Add CI/CD gitea workflows. -- Add Helm Chart for easy deployment. -- Add integration tests that run against a kind cluster to validate in-cluster behaviour. -- Add a long-running informer with event handlers to react to lease changes instead of polling. - ## License - See the `LICENSE` file in this repository. diff --git a/internal/informer_test.go b/internal/informer_test.go new file mode 100644 index 0000000..6f1dff3 --- /dev/null +++ b/internal/informer_test.go @@ -0,0 +1,35 @@ +package internal + +import ( + "testing" + + coordv1 "k8s.io/api/coordination/v1" +) + +func TestHandleLease_DryRun(t *testing.T) { + l := &coordv1.Lease{} + l.Name = "cilium-l2announce-abc" + hi := "xx-yy-node-a" + l.Spec.HolderIdentity = &hi + + cfg := &Config{CiliumLabel: "lbl", DryRun: true} + + handleLease(l, cfg) +} + +func TestHandleLease_NonCilium(t *testing.T) { + l := &coordv1.Lease{} + l.Name = "other-lease" + hi := "xx-yy-node-a" + l.Spec.HolderIdentity = &hi + + cfg := &Config{CiliumLabel: "lbl", DryRun: true} + handleLease(l, cfg) +} + +func TestHandleLease_NoHolderIdentity(t *testing.T) { + l := &coordv1.Lease{} + l.Name = "cilium-l2announce-123" + cfg := &Config{CiliumLabel: "lbl", DryRun: true} + handleLease(l, cfg) +} diff --git a/internal/kube.go b/internal/kube.go index eaa0455..cc09892 100644 --- a/internal/kube.go +++ b/internal/kube.go @@ -28,14 +28,18 @@ func generateClient() (kubernetes.Clientset, error) { } 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()) } + return ApplyLabelToNode(&client, nodeName, leaseName, cfg) + +} + +func ApplyLabelToNode(client kubernetes.Interface, nodeName string, leaseName string, cfg *Config) error { log.WithFields(log.Fields{ - "Caller": "LabelNode", + "Caller": "ApplyLabelToNode", }).Info(fmt.Sprintf("Trying to label node %s", nodeName)) node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) @@ -56,11 +60,10 @@ func LabelNode(nodeName string, leaseName string, cfg *Config) error { } log.WithFields(log.Fields{ - "Caller": "LabelNode", + "Caller": "ApplyLabelToNode", }).Info(fmt.Sprintf("Node %s labeled with %s=%s\n", nodeName, cfg.CiliumLabel, "true")) return nil - } func RemoveLabelFromNode(nodeName string) error { @@ -92,37 +95,6 @@ func RemoveLabelFromNode(nodeName string) error { 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) diff --git a/internal/kube_test.go b/internal/kube_test.go new file mode 100644 index 0000000..39404b7 --- /dev/null +++ b/internal/kube_test.go @@ -0,0 +1,73 @@ +package internal + +import ( + "context" + "testing" + + coordv1 "k8s.io/api/coordination/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestGetNodeNameFromLease_Valid(t *testing.T) { + l := coordv1.Lease{} + l.Name = "cilium-l2announce-1" + hi := "aa-bb-node1" + l.Spec.HolderIdentity = &hi + + node, err := GetNodeNameFromLease(l) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if node != "node1" { + t.Fatalf("unexpected node name: got %s", node) + } +} + +func TestGetNodeNameFromLease_Invalid(t *testing.T) { + l := coordv1.Lease{} + l.Name = "cilium-l2announce-2" + hi := "too-short" + l.Spec.HolderIdentity = &hi + + _, err := GetNodeNameFromLease(l) + if err == nil { + t.Fatalf("expected error for invalid holder identity") + } +} + +func TestApplyLabelToNode_WithFakeClient(t *testing.T) { + client := fake.NewSimpleClientset() + + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + Labels: map[string]string{}, + }, + } + _, err := client.CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create fake node: %v", err) + } + + cfg := &Config{CiliumLabel: "mylabel", DryRun: false} + + err = ApplyLabelToNode(client, "test-node", "lease-1", cfg) + if err != nil { + t.Fatalf("ApplyLabelToNode failed: %v", err) + } + + updated, err := client.CoreV1().Nodes().Get(context.TODO(), "test-node", metav1.GetOptions{}) + if err != nil { + t.Fatalf("failed to get node: %v", err) + } + + if updated.Labels["mylabel"] != "true" { + t.Fatalf("expected label set on node, got: %v", updated.Labels) + } + + if updated.Labels["cilium.uploadfilter24.eu/lease"] != "lease-1" { + t.Fatalf("expected lease label set on node, got: %v", updated.Labels) + } +}