package ingress import ( "context" "time" ngrok "github.com/ngrok/ngrok-api-go/v7" ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) var _ = Describe("IPPolicyReconciler", func() { const ( timeout = 10 * time.Second interval = 250 * time.Millisecond ) var ( ctx context.Context ) BeforeEach(func() { ctx = GinkgoT().Context() }) AfterEach(func() { ipPolicyClient.Reset() }) It("creates IPPolicy and configures rules", func() { ip := &ingressv1alpha1.IPPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "test-ip-policy", Namespace: "default"}, Spec: ingressv1alpha1.IPPolicySpec{ Metadata: "test", }, } ip.Spec.Rules = []ingressv1alpha1.IPPolicyRule{ {CIDR: "10.0.0.0/8", Action: "allow", Description: "desc1"}, {CIDR: "192.168.1.0/24", Action: "deny", Description: "desc2"}, } Expect(k8sClient.Create(ctx, ip)).To(Succeed()) Eventually(func() []string { items := ipPolicyRuleClient.Items() out := []string{} for _, it := range items { out = append(out, it.CIDR) } return out }, timeout, interval).Should(ContainElements("10.0.0.0/8", "192.168.1.0/24")) }) It("updates existing rule descriptions", func() { ip := &ingressv1alpha1.IPPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "test-ip-policy-update", Namespace: "default"}, Spec: ingressv1alpha1.IPPolicySpec{ Metadata: "test", }, } ip.Spec.Rules = []ingressv1alpha1.IPPolicyRule{ {CIDR: "10.0.0.0/8", Action: "allow", Description: "orig"}, } Expect(k8sClient.Create(ctx, ip)).To(Succeed()) Eventually(func() []string { items := ipPolicyRuleClient.Items() out := []string{} for _, it := range items { out = append(out, it.Description) } return out }, timeout, interval).Should(ContainElement("orig")) patch := client.MergeFrom(ip.DeepCopy()) ip.Spec.Rules[0].Description = "updated" Expect(k8sClient.Patch(ctx, ip, patch)).To(Succeed()) Eventually(func() []string { items := ipPolicyRuleClient.Items() out := []string{} for _, it := range items { out = append(out, it.Description) } return out }, timeout, interval).Should(ContainElement("updated")) }) It("deletes obsolete rules", func() { ip := &ingressv1alpha1.IPPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "test-ip-policy-del", Namespace: "default"}, Spec: ingressv1alpha1.IPPolicySpec{ Metadata: "test", }, } ip.Spec.Rules = []ingressv1alpha1.IPPolicyRule{ {CIDR: "10.0.0.0/8", Action: "allow", Description: "orig"}, } Expect(k8sClient.Create(ctx, ip)).To(Succeed()) Eventually(func() []string { items := ipPolicyRuleClient.Items() out := []string{} for _, it := range items { out = append(out, it.CIDR) } return out }, timeout, interval).Should(ContainElement("10.0.0.0/8")) patch := client.MergeFrom(ip.DeepCopy()) ip.Spec.Rules = []ingressv1alpha1.IPPolicyRule{} Expect(k8sClient.Patch(ctx, ip, patch)).To(Succeed()) Eventually(func() int { count := 0 for _, it := range ipPolicyRuleClient.Items() { if it.IPPolicy.ID == ip.Status.ID { count++ } } return count }, timeout, interval).Should(Equal(0)) }) It("sets Created condition for existing IP Policy", func() { By("creating an IP Policy") ip := &ingressv1alpha1.IPPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "test-existing-policy", Namespace: "default"}, Spec: ingressv1alpha1.IPPolicySpec{ Metadata: "test", }, } ip.Spec.Description = "test-existing" Expect(k8sClient.Create(ctx, ip)).To(Succeed()) By("waiting for the IP Policy to be created") kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyCreated, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyReady, metav1.ConditionTrue) By("clearning the status conditions to simulate existing ip policy") Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(ip), ip) g.Expect(err).NotTo(HaveOccurred()) ip.Status.Conditions = []metav1.Condition{} err = k8sClient.Status().Update(ctx, ip) g.Expect(err).NotTo(HaveOccurred()) }, timeout, interval).Should(Succeed()) By("verifying the created and ready conditions are set again") kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyCreated, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyReady, metav1.ConditionTrue) }) It("sets Created condition for existing IP Policy with rules", func() { By("creating an IPPolicy resource with rules") ip := &ingressv1alpha1.IPPolicy{ ObjectMeta: metav1.ObjectMeta{Name: "test-existing-policy-rules", Namespace: "default"}, Spec: ingressv1alpha1.IPPolicySpec{ Metadata: "test", }, } ip.Spec.Description = "test-with-rules" ip.Spec.Rules = []ingressv1alpha1.IPPolicyRule{{CIDR: "10.0.0.0/8", Action: "allow"}} Expect(k8sClient.Create(ctx, ip)).To(Succeed()) By("waiting for the IP Policy to be created") kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyCreated, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyReady, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyRulesConfigured, metav1.ConditionTrue) By("clearing the status conditions to simulate an existing ip policy") Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(ip), ip) g.Expect(err).NotTo(HaveOccurred()) ip.Status.Conditions = []metav1.Condition{} err = k8sClient.Status().Update(ctx, ip) g.Expect(err).NotTo(HaveOccurred()) }, timeout, interval).Should(Succeed()) By("verifying the created, rules configured, and ready conditions are set again") kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyCreated, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyReady, metav1.ConditionTrue) kginkgo.EventuallyIPPolicyHasCondition(ctx, ip, ConditionIPPolicyRulesConfigured, metav1.ConditionTrue) }) }) var _ = Describe("IPPolicyDiff", func() { It("computes creates, deletes and updates correctly", func() { remoteRules := []*ngrok.IPPolicyRule{ {ID: "1", CIDR: "192.168.1.0/25", Action: IPPolicyRuleActionAllow, Description: "a"}, {ID: "2", CIDR: "192.168.128.0/25", Action: IPPolicyRuleActionDeny, Description: "aa"}, {ID: "3", CIDR: "172.16.0.0/16", Action: IPPolicyRuleActionAllow, Description: "aaa"}, {ID: "4", CIDR: "172.17.0.0/16", Action: IPPolicyRuleActionDeny, Description: "aaaa"}, {ID: "5", CIDR: "172.19.0.0/16", Action: IPPolicyRuleActionAllow, Description: "aaaaa"}, } changedDescriptionRule := ingressv1alpha1.IPPolicyRule{ CIDR: "172.19.0.0/16", Action: IPPolicyRuleActionAllow, Description: "b", } specRules := []ingressv1alpha1.IPPolicyRule{ {CIDR: "192.168.1.0/25", Action: IPPolicyRuleActionDeny}, {CIDR: "192.168.128.0/25", Action: IPPolicyRuleActionAllow}, {CIDR: "10.0.0.0/8", Action: IPPolicyRuleActionDeny}, {CIDR: "172.18.0.0/16", Action: IPPolicyRuleActionAllow}, changedDescriptionRule, } diff := newIPPolicyDiff("test", remoteRules, specRules) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsDelete()).To(BeEmpty()) Expect(diff.NeedsUpdate()).To(BeEmpty()) Expect(diff.NeedsCreate()).To(Equal([]*ngrok.IPPolicyRuleCreate{{IPPolicyID: "test", CIDR: specRules[2].CIDR, Action: new(IPPolicyRuleActionDeny)}})) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsUpdate()).To(BeEmpty()) Expect(diff.NeedsDelete()).To(Equal([]*ngrok.IPPolicyRule{remoteRules[0]})) Expect(diff.NeedsCreate()).To(Equal([]*ngrok.IPPolicyRuleCreate{{IPPolicyID: "test", CIDR: specRules[0].CIDR, Action: new(IPPolicyRuleActionDeny)}})) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsUpdate()).To(BeEmpty()) Expect(diff.NeedsDelete()).To(Equal([]*ngrok.IPPolicyRule{remoteRules[1]})) Expect(diff.NeedsCreate()).To(Equal([]*ngrok.IPPolicyRuleCreate{{IPPolicyID: "test", CIDR: specRules[1].CIDR, Action: new(IPPolicyRuleActionAllow)}})) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsUpdate()).To(BeEmpty()) Expect(diff.NeedsDelete()).To(BeEmpty()) Expect(diff.NeedsCreate()).To(Equal([]*ngrok.IPPolicyRuleCreate{{IPPolicyID: "test", CIDR: specRules[3].CIDR, Action: new(IPPolicyRuleActionAllow)}})) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsUpdate()).To(BeEmpty()) Expect(diff.NeedsDelete()).To(Equal([]*ngrok.IPPolicyRule{remoteRules[2], remoteRules[3]})) Expect(diff.NeedsCreate()).To(BeEmpty()) Expect(diff.Next()).To(BeTrue()) Expect(diff.NeedsDelete()).To(BeEmpty()) Expect(diff.NeedsCreate()).To(BeEmpty()) Expect(diff.NeedsUpdate()).To(Equal([]*ngrok.IPPolicyRuleUpdate{{ID: "5", CIDR: new("172.19.0.0/16"), Description: new("b"), Metadata: new("")}})) Expect(diff.Next()).To(BeFalse()) }) })