Skip to content
This repository was archived by the owner on Dec 3, 2025. It is now read-only.

Commit a82d2bb

Browse files
authored
feat: support ClusterIP service (#201)
* generate clusterIP * generate manifest * add test * fix linting
1 parent 5e67a62 commit a82d2bb

4 files changed

Lines changed: 171 additions & 5 deletions

File tree

config/crd/bases/vpn.wireguard-operator.io_wireguards.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ spec:
196196
type: object
197197
serviceType:
198198
description: A field that specifies the type of Kubernetes service
199-
that should be used for the Wireguard VPN. This could be NodePort
200-
or LoadBalancer, depending on the needs of the deployment.
199+
that should be used for the Wireguard VPN. This could be ClusterIP,
200+
NodePort or LoadBalancer, depending on the needs of the deployment.
201201
type: string
202202
useWgUserspaceImplementation:
203203
description: A boolean field that specifies whether to use the userspace

pkg/api/v1alpha1/wireguard_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type WireguardSpec struct {
4343
Address string `json:"address,omitempty"`
4444
// A string field that specifies the DNS server(s) to be used by the peers.
4545
Dns string `json:"dns,omitempty"`
46-
// A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be NodePort or LoadBalancer, depending on the needs of the deployment.
46+
// A field that specifies the type of Kubernetes service that should be used for the Wireguard VPN. This could be ClusterIP, NodePort or LoadBalancer, depending on the needs of the deployment.
4747
ServiceType corev1.ServiceType `json:"serviceType,omitempty"`
4848
// A field that specifies the value to use for a nodePort ServiceType
4949
NodePort int32 `json:"port,omitempty"`

pkg/controllers/wireguard_controller.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,17 @@ func (r *WireguardReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
439439

440440
}
441441

442+
if serviceType == corev1.ServiceTypeClusterIP {
443+
if len(svcFound.Spec.Ports) == 0 {
444+
err = r.updateStatus(ctx, req, wireguard, v1alpha1.WgStatusReport{Status: v1alpha1.Pending, Message: "Waiting for service with type ClusterIP to be ready"})
445+
if err != nil {
446+
return ctrl.Result{}, err
447+
}
448+
449+
return ctrl.Result{}, nil
450+
}
451+
}
452+
442453
if wireguard.Status.Address != address || port != wireguard.Status.Port || dnsAddress != wireguard.Status.Dns {
443454
updateWireguard := wireguard.DeepCopy()
444455
updateWireguard.Status.Address = address

pkg/controllers/wireguard_controller_test.go

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import (
88
"time"
99

1010
"github.com/jodevsa/wireguard-operator/pkg/api/v1alpha1"
11-
"sigs.k8s.io/controller-runtime/pkg/client"
12-
1311
. "github.com/onsi/ginkgo"
1412
. "github.com/onsi/gomega"
1513
appsv1 "k8s.io/api/apps/v1"
1614
corev1 "k8s.io/api/core/v1"
1715
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1816
"k8s.io/apimachinery/pkg/types"
17+
"k8s.io/apimachinery/pkg/util/intstr"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
1919
)
2020

2121
// test helpers
@@ -65,6 +65,21 @@ func reconcileServiceWithTypeLoadBalancer(svcKey client.ObjectKey, hostname stri
6565
return k8sClient.Status().Update(context.Background(), svc)
6666
}
6767

68+
func reconcileServiceWithClusterIP(svcKey client.ObjectKey, port int32) error {
69+
svc := &corev1.Service{}
70+
Expect(k8sClient.Get(context.Background(), svcKey, svc)).Should(Succeed())
71+
72+
if svc.Spec.Type != corev1.ServiceTypeClusterIP {
73+
return fmt.Errorf("ReconcileServiceWithClusterIP only reconsiles ClusterIP services")
74+
}
75+
76+
svc.Spec.Ports = []corev1.ServicePort{{
77+
Port: port,
78+
TargetPort: intstr.FromInt32(port),
79+
}}
80+
return k8sClient.Status().Update(context.Background(), svc)
81+
}
82+
6883
var _ = Describe("wireguard controller", func() {
6984

7085
// Define utility constants for object names and testing timeouts/durations and intervals.
@@ -515,6 +530,146 @@ Endpoint = %s:%s"`, peerKey.Name, peer.Spec.AllowedIPs, peer.Spec.Address, dnsSe
515530

516531
})
517532

533+
It("Should create a WG with ServiceType ClusterIP and WG peer successfully", func() {
534+
expectedAddress := "test-address"
535+
536+
wgKey := types.NamespacedName{
537+
Name: wgName,
538+
Namespace: wgNamespace,
539+
}
540+
created := &v1alpha1.Wireguard{
541+
ObjectMeta: metav1.ObjectMeta{
542+
Name: wgKey.Name,
543+
Namespace: wgKey.Namespace,
544+
},
545+
Spec: v1alpha1.WireguardSpec{
546+
ServiceType: corev1.ServiceTypeClusterIP,
547+
Address: expectedAddress,
548+
},
549+
}
550+
expectedLabels := map[string]string{"app": "wireguard", "instance": wgKey.Name}
551+
552+
Expect(k8sClient.Create(context.Background(), created)).Should(Succeed())
553+
554+
// service created
555+
serviceName := wgKey.Name + "-svc"
556+
serviceKey := types.NamespacedName{
557+
Namespace: wgKey.Namespace,
558+
Name: serviceName,
559+
}
560+
561+
// match labels
562+
Eventually(func() map[string]string {
563+
svc := &corev1.Service{}
564+
//nolint:errcheck
565+
k8sClient.Get(context.Background(), serviceKey, svc)
566+
return svc.Spec.Selector
567+
}, Timeout, Interval).Should(BeEquivalentTo(expectedLabels))
568+
569+
// match service type
570+
Eventually(func() corev1.ServiceType {
571+
svc := &corev1.Service{}
572+
//nolint:errcheck
573+
k8sClient.Get(context.Background(), serviceKey, svc)
574+
return svc.Spec.Type
575+
}, Timeout, Interval).Should(Equal(corev1.ServiceTypeClusterIP))
576+
577+
Eventually(func() v1alpha1.WireguardStatus {
578+
wg := &v1alpha1.Wireguard{}
579+
//nolint:errcheck
580+
k8sClient.Get(context.Background(), wgKey, wg)
581+
return wg.Status
582+
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{
583+
Address: "",
584+
Status: "pending",
585+
Message: "Waiting for service to be ready",
586+
}))
587+
588+
Expect(reconcileServiceWithClusterIP(serviceKey, 51820)).Should(Succeed())
589+
590+
// check that wireguard resource got the right status after the service is ready
591+
wg := &v1alpha1.Wireguard{}
592+
Eventually(func() v1alpha1.WireguardStatus {
593+
Expect(k8sClient.Get(context.Background(), wgKey, wg)).Should(Succeed())
594+
return wg.Status
595+
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardStatus{
596+
Address: expectedAddress,
597+
Port: "51820",
598+
Status: "ready",
599+
Dns: dnsServiceIp,
600+
Message: "VPN is active!",
601+
}))
602+
603+
Eventually(func() string {
604+
deploymentKey := types.NamespacedName{
605+
Name: wgName + "-dep",
606+
Namespace: wgNamespace,
607+
}
608+
deployment := &appsv1.Deployment{}
609+
Expect(k8sClient.Get(context.Background(), deploymentKey, deployment)).Should(Succeed())
610+
Expect(len(deployment.Spec.Template.Spec.Containers)).Should(Equal(2))
611+
Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(Equal(deployment.Spec.Template.Spec.Containers[1].Image))
612+
return deployment.Spec.Template.Spec.Containers[0].Image
613+
}, Timeout, Interval).Should(Equal(wgTestImage))
614+
615+
// create peer
616+
peerKey := types.NamespacedName{
617+
Name: wgKey.Name + "peer",
618+
Namespace: wgKey.Namespace,
619+
}
620+
peer := &v1alpha1.WireguardPeer{
621+
ObjectMeta: metav1.ObjectMeta{
622+
Name: peerKey.Name,
623+
Namespace: peerKey.Namespace,
624+
},
625+
Spec: v1alpha1.WireguardPeerSpec{
626+
WireguardRef: wgKey.Name,
627+
},
628+
}
629+
Expect(k8sClient.Create(context.Background(), peer)).Should(Succeed())
630+
631+
//get peer secret
632+
wgSecretKeyName := types.NamespacedName{
633+
Name: wgKey.Name,
634+
Namespace: wgKey.Namespace,
635+
}
636+
wgSecret := &corev1.Secret{}
637+
Eventually(func() error {
638+
return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret)
639+
}, Timeout, Interval).Should(Succeed())
640+
wgPublicKey := string(wgSecret.Data["publicKey"])
641+
642+
Eventually(func() string {
643+
Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed())
644+
print(peer.Status.Message)
645+
return peer.Spec.Address
646+
}, Timeout, Interval).Should(Equal("10.8.0.2"))
647+
648+
Eventually(func() v1alpha1.WireguardPeerStatus {
649+
Expect(k8sClient.Get(context.Background(), peerKey, peer)).Should(Succeed())
650+
return peer.Status
651+
}, Timeout, Interval).Should(Equal(v1alpha1.WireguardPeerStatus{
652+
Config: fmt.Sprintf(`
653+
echo "
654+
[Interface]
655+
PrivateKey = $(kubectl get secret %s-peer --template={{.data.privateKey}} -n default | base64 -d)
656+
Address = %s
657+
DNS = %s, %s.svc.cluster.local
658+
659+
[Peer]
660+
PublicKey = %s
661+
AllowedIPs = 0.0.0.0/0
662+
Endpoint = %s:%s"`, peerKey.Name, peer.Spec.Address, dnsServiceIp, peer.Namespace, wgPublicKey, expectedAddress, wg.Status.Port),
663+
Status: "ready",
664+
Message: "Peer configured",
665+
}))
666+
667+
Eventually(func() error {
668+
return k8sClient.Get(context.Background(), wgSecretKeyName, wgSecret)
669+
}, Timeout, Interval).Should(Succeed())
670+
671+
})
672+
518673
for _, useWgUserspace := range []bool{true, false} {
519674
testTextPrefix := "uses"
520675
if !useWgUserspace {

0 commit comments

Comments
 (0)