Skip to content

Commit ee09021

Browse files
Allow to configure both PreLogin and PostLogin Banners (#136)
1 parent b48c1bc commit ee09021

File tree

11 files changed

+214
-42
lines changed

11 files changed

+214
-42
lines changed

api/core/v1alpha1/banner_types.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,31 @@ type BannerSpec struct {
2020
// +optional
2121
ProviderConfigRef *TypedLocalObjectReference `json:"providerConfigRef,omitempty"`
2222

23-
// Pre-Login banner to display on login.
23+
// Type specifies the banner type to configure, either PreLogin or PostLogin.
24+
// Immutable.
25+
// +optional
26+
// +kubebuilder:default=PreLogin
27+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Type is immutable"
28+
Type BannerType `json:"type,omitempty"`
29+
30+
// Message is the banner message to display.
2431
// +required
2532
Message TemplateSource `json:"message"`
2633
}
2734

35+
// BannerType represents the type of banner to configure
36+
// +kubebuilder:validation:Enum=PreLogin;PostLogin
37+
type BannerType string
38+
39+
const (
40+
// BannerTypePreLogin represents the login banner displayed before user authentication.
41+
// This corresponds to the openconfig-system login-banner leaf.
42+
BannerTypePreLogin BannerType = "PreLogin"
43+
// BannerTypePostLogin represents the message banner displayed after user authentication.
44+
// This corresponds to the openconfig-system motd-banner leaf.
45+
BannerTypePostLogin BannerType = "PostLogin"
46+
)
47+
2848
// BannerStatus defines the observed state of Banner.
2949
type BannerStatus struct {
3050
// The conditions are a list of status objects that describe the state of the Banner.

charts/network-operator/templates/crd/networking.metal.ironcore.dev_banners.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ spec:
7777
- message: DeviceRef is immutable
7878
rule: self == oldSelf
7979
message:
80-
description: Pre-Login banner to display on login.
80+
description: Message is the banner message to display.
8181
properties:
8282
configMapRef:
8383
description: Reference to a ConfigMap containing the template
@@ -179,6 +179,18 @@ spec:
179179
- name
180180
type: object
181181
x-kubernetes-map-type: atomic
182+
type:
183+
default: PreLogin
184+
description: |-
185+
Type specifies the banner type to configure, either PreLogin or PostLogin.
186+
Immutable.
187+
enum:
188+
- PreLogin
189+
- PostLogin
190+
type: string
191+
x-kubernetes-validations:
192+
- message: Type is immutable
193+
rule: self == oldSelf
182194
required:
183195
- deviceRef
184196
- message

config/crd/bases/networking.metal.ironcore.dev_banners.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ spec:
7171
- message: DeviceRef is immutable
7272
rule: self == oldSelf
7373
message:
74-
description: Pre-Login banner to display on login.
74+
description: Message is the banner message to display.
7575
properties:
7676
configMapRef:
7777
description: Reference to a ConfigMap containing the template
@@ -173,6 +173,18 @@ spec:
173173
- name
174174
type: object
175175
x-kubernetes-map-type: atomic
176+
type:
177+
default: PreLogin
178+
description: |-
179+
Type specifies the banner type to configure, either PreLogin or PostLogin.
180+
Immutable.
181+
enum:
182+
- PreLogin
183+
- PostLogin
184+
type: string
185+
x-kubernetes-validations:
186+
- message: Type is immutable
187+
rule: self == oldSelf
176188
required:
177189
- deviceRef
178190
- message

config/samples/v1alpha1_banner.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ metadata:
99
spec:
1010
deviceRef:
1111
name: leaf1
12+
type: PreLogin
1213
message:
1314
inline: |
1415
###################################################

internal/controller/core/banner_controller.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,9 @@ func (r *BannerReconciler) reconcile(ctx context.Context, s *bannerScope) (reter
245245
}
246246

247247
// Ensure the Banner is realized on the provider.
248-
err = s.Provider.EnsureBanner(ctx, &provider.BannerRequest{
248+
err = s.Provider.EnsureBanner(ctx, &provider.EnsureBannerRequest{
249249
Message: string(msg),
250+
Type: s.Banner.Spec.Type,
250251
ProviderConfig: s.ProviderConfig,
251252
})
252253

@@ -268,7 +269,9 @@ func (r *BannerReconciler) finalize(ctx context.Context, s *bannerScope) (reterr
268269
}
269270
}()
270271

271-
return s.Provider.DeleteBanner(ctx)
272+
return s.Provider.DeleteBanner(ctx, &provider.DeleteBannerRequest{
273+
Type: s.Banner.Spec.Type,
274+
})
272275
}
273276

274277
// secretToBanner is a [handler.MapFunc] to be used to enqueue requests for reconciliation

internal/controller/core/banner_controller_test.go

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,6 @@ var _ = Describe("Banner Controller", func() {
3737
}
3838
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
3939
}
40-
41-
By("Creating the custom resource for the Kind Banner")
42-
banner := &v1alpha1.Banner{}
43-
if err := k8sClient.Get(ctx, key, banner); errors.IsNotFound(err) {
44-
resource := &v1alpha1.Banner{
45-
ObjectMeta: metav1.ObjectMeta{
46-
Name: name,
47-
Namespace: metav1.NamespaceDefault,
48-
},
49-
Spec: v1alpha1.BannerSpec{
50-
DeviceRef: v1alpha1.LocalObjectReference{Name: name},
51-
Message: v1alpha1.TemplateSource{
52-
Inline: ptr.To("Test Banner"),
53-
},
54-
},
55-
}
56-
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
57-
}
5840
})
5941

6042
AfterEach(func() {
@@ -74,11 +56,87 @@ var _ = Describe("Banner Controller", func() {
7456

7557
By("Ensuring the resource is deleted from the provider")
7658
Eventually(func(g Gomega) {
77-
g.Expect(testProvider.Banner).To(BeNil(), "Provider Banner should be nil")
59+
g.Expect(testProvider.PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil")
60+
g.Expect(testProvider.PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil")
61+
}).Should(Succeed())
62+
})
63+
64+
It("Should successfully reconcile a PreLogin Banner", func() {
65+
By("Creating a PreLogin Banner")
66+
banner := &v1alpha1.Banner{
67+
ObjectMeta: metav1.ObjectMeta{
68+
Name: name,
69+
Namespace: metav1.NamespaceDefault,
70+
},
71+
Spec: v1alpha1.BannerSpec{
72+
DeviceRef: v1alpha1.LocalObjectReference{Name: name},
73+
Type: v1alpha1.BannerTypePreLogin,
74+
Message: v1alpha1.TemplateSource{
75+
Inline: ptr.To("Test Banner"),
76+
},
77+
},
78+
}
79+
Expect(k8sClient.Create(ctx, banner)).To(Succeed())
80+
81+
By("Adding a finalizer to the resource")
82+
Eventually(func(g Gomega) {
83+
resource := &v1alpha1.Banner{}
84+
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
85+
g.Expect(controllerutil.ContainsFinalizer(resource, v1alpha1.FinalizerName)).To(BeTrue())
86+
}).Should(Succeed())
87+
88+
By("Adding the device label to the resource")
89+
Eventually(func(g Gomega) {
90+
resource := &v1alpha1.Banner{}
91+
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
92+
g.Expect(resource.Labels).To(HaveKeyWithValue(v1alpha1.DeviceLabel, name))
93+
}).Should(Succeed())
94+
95+
By("Adding the device as a owner reference")
96+
Eventually(func(g Gomega) {
97+
resource := &v1alpha1.Banner{}
98+
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
99+
g.Expect(resource.OwnerReferences).To(HaveLen(1))
100+
g.Expect(resource.OwnerReferences[0].Kind).To(Equal("Device"))
101+
g.Expect(resource.OwnerReferences[0].Name).To(Equal(name))
102+
}).Should(Succeed())
103+
104+
By("Updating the resource status")
105+
Eventually(func(g Gomega) {
106+
resource := &v1alpha1.Banner{}
107+
g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
108+
g.Expect(resource.Status.Conditions).To(HaveLen(1))
109+
g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition))
110+
g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue))
111+
}).Should(Succeed())
112+
113+
By("Ensuring the resource is created in the provider")
114+
Eventually(func(g Gomega) {
115+
g.Expect(testProvider.PreLoginBanner).ToNot(BeNil(), "Provider Banner should not be nil")
116+
g.Expect(testProvider.PostLoginBanner).To(BeNil(), "Provider PostLogin Banner should be nil")
117+
if testProvider.PreLoginBanner != nil {
118+
g.Expect(*testProvider.PreLoginBanner).To(Equal("Test Banner"))
119+
}
78120
}).Should(Succeed())
79121
})
80122

81-
It("Should successfully reconcile the resource", func() {
123+
It("Should successfully reconcile a PostLogin Banner", func() {
124+
By("Creating a PostLogin Banner")
125+
banner := &v1alpha1.Banner{
126+
ObjectMeta: metav1.ObjectMeta{
127+
Name: name,
128+
Namespace: metav1.NamespaceDefault,
129+
},
130+
Spec: v1alpha1.BannerSpec{
131+
DeviceRef: v1alpha1.LocalObjectReference{Name: name},
132+
Type: v1alpha1.BannerTypePostLogin,
133+
Message: v1alpha1.TemplateSource{
134+
Inline: ptr.To("Test Banner"),
135+
},
136+
},
137+
}
138+
Expect(k8sClient.Create(ctx, banner)).To(Succeed())
139+
82140
By("Adding a finalizer to the resource")
83141
Eventually(func(g Gomega) {
84142
resource := &v1alpha1.Banner{}
@@ -113,9 +171,10 @@ var _ = Describe("Banner Controller", func() {
113171

114172
By("Ensuring the resource is created in the provider")
115173
Eventually(func(g Gomega) {
116-
g.Expect(testProvider.Banner).ToNot(BeNil(), "Provider Banner should not be nil")
117-
if testProvider.Banner != nil {
118-
g.Expect(*testProvider.Banner).To(Equal("Test Banner"))
174+
g.Expect(testProvider.PreLoginBanner).To(BeNil(), "Provider PreLogin Banner should be nil")
175+
g.Expect(testProvider.PostLoginBanner).ToNot(BeNil(), "Provider PostLogin Banner should not be nil")
176+
if testProvider.PostLoginBanner != nil {
177+
g.Expect(*testProvider.PostLoginBanner).To(Equal("Test Banner"))
119178
}
120179
}).Should(Succeed())
121180
})

internal/controller/core/suite_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package core
55

66
import (
77
"context"
8+
"errors"
89
"os"
910
"path/filepath"
1011
"strconv"
@@ -346,7 +347,8 @@ type Provider struct {
346347

347348
Ports sets.Set[string]
348349
User sets.Set[string]
349-
Banner *string
350+
PreLoginBanner *string
351+
PostLoginBanner *string
350352
DNS *v1alpha1.DNS
351353
NTP *v1alpha1.NTP
352354
ACLs sets.Set[string]
@@ -427,17 +429,31 @@ func (p *Provider) GetInterfaceStatus(context.Context, *provider.InterfaceReques
427429
}, nil
428430
}
429431

430-
func (p *Provider) EnsureBanner(_ context.Context, req *provider.BannerRequest) error {
432+
func (p *Provider) EnsureBanner(_ context.Context, req *provider.EnsureBannerRequest) error {
431433
p.Lock()
432434
defer p.Unlock()
433-
p.Banner = &req.Message
435+
switch req.Type {
436+
case v1alpha1.BannerTypePreLogin:
437+
p.PreLoginBanner = &req.Message
438+
case v1alpha1.BannerTypePostLogin:
439+
p.PostLoginBanner = &req.Message
440+
default:
441+
return errors.New("unknown banner type")
442+
}
434443
return nil
435444
}
436445

437-
func (p *Provider) DeleteBanner(context.Context) error {
446+
func (p *Provider) DeleteBanner(_ context.Context, req *provider.DeleteBannerRequest) error {
438447
p.Lock()
439448
defer p.Unlock()
440-
p.Banner = nil
449+
switch req.Type {
450+
case v1alpha1.BannerTypePreLogin:
451+
p.PreLoginBanner = nil
452+
case v1alpha1.BannerTypePostLogin:
453+
p.PostLoginBanner = nil
454+
default:
455+
return errors.New("unknown banner type")
456+
}
441457
return nil
442458
}
443459

internal/provider/cisco/nxos/banner.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33

44
package nxos
55

6-
import "github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
6+
import (
7+
"fmt"
8+
9+
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
10+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
11+
)
712

813
var (
914
_ gnmiext.Configurable = (*Banner)(nil)
@@ -16,13 +21,40 @@ type Banner struct {
1621
Delimiter string `json:"delimiter"`
1722
// String to be displayed as the banner message
1823
Message string `json:"message"`
24+
// Type indicates whether this is a pre-login or post-login banner.
25+
// This field is not serialized to JSON and is only used internally
26+
// to determine the correct XPath for the banner configuration.
27+
Type BannerType `json:"-"`
1928
}
2029

21-
func (*Banner) XPath() string {
30+
func (b *Banner) XPath() string {
31+
if b.Type == PostLogin {
32+
return "System/userext-items/postloginbanner-items"
33+
}
2234
return "System/userext-items/preloginbanner-items"
2335
}
2436

2537
func (b *Banner) Default() {
2638
b.Delimiter = "#"
27-
b.Message = "User Access Verification\n"
39+
if b.Type == PreLogin {
40+
b.Message = "User Access Verification\n"
41+
}
42+
}
43+
44+
type BannerType string
45+
46+
const (
47+
PreLogin BannerType = "prelogin"
48+
PostLogin BannerType = "postlogin"
49+
)
50+
51+
func BannerTypeFrom(bt v1alpha1.BannerType) (BannerType, error) {
52+
switch bt {
53+
case v1alpha1.BannerTypePreLogin:
54+
return PreLogin, nil
55+
case v1alpha1.BannerTypePostLogin:
56+
return PostLogin, nil
57+
default:
58+
return "", fmt.Errorf("unknown banner type: %s", bt)
59+
}
2860
}

internal/provider/cisco/nxos/banner_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
package nxos
55

66
func init() {
7-
Register("banner", &Banner{Delimiter: "^", Message: "Test Banner"})
7+
Register("banner", &Banner{Delimiter: "^", Message: "Test Banner", Type: PreLogin})
88
}

0 commit comments

Comments
 (0)