Skip to content

Commit cb7b9e6

Browse files
authored
Add Notification Contacts CRDs and admission webhooks (#294)
## Overview This pull-request introduces the first set of **Notification Contacts** building blocks. It adds CustomResourceDefinitions (CRDs) and admission webhooks that allow the platform to manage e-mail contacts and their grouping in a safe and extensible way. ### What’s included 1. **CRDs** * `Contact` – stores the personal data and preferred e-mail of a subject * `ContactGroup` – logical grouping of contacts (e.g. _Admins_, _Owners_) * `ContactGroupMembership` – joins a `Contact` to a `ContactGroup` * `ContactGroupMembershipRemoval` – request object used to remove a membership through approval flow 2. **Admission Webhooks** * **Validation** webhooks for *create* / *update* ensuring schema coherence, referential integrity and immutability guarantees * **Mutation** webhooks adding defaults and calculated labels/annotations (e.g. `spec.displayName` fallback, owner references) 3. **ProtectedResource Manifests** * Added `ProtectedResource` objects for each new kind to wire them into IAM RBAC
2 parents d9e50eb + 5aa5088 commit cb7b9e6

File tree

50 files changed

+4864
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4864
-1
lines changed

cmd/milo/controller-manager/controllermanager.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,22 @@ func Run(ctx context.Context, c *config.CompletedConfig, opts *Options) error {
432432
logger.Error(err, "Error setting up user invitation webhook")
433433
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
434434
}
435+
if err := notificationv1alpha1webhook.SetupContactWebhooksWithManager(ctrl); err != nil {
436+
logger.Error(err, "Error setting up contact webhook")
437+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
438+
}
439+
if err := notificationv1alpha1webhook.SetupContactGroupWebhooksWithManager(ctrl); err != nil {
440+
logger.Error(err, "Error setting up contactgroup webhook")
441+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
442+
}
443+
if err := notificationv1alpha1webhook.SetupContactGroupMembershipWebhooksWithManager(ctrl); err != nil {
444+
logger.Error(err, "Error setting up contactgroupmembership webhook")
445+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
446+
}
447+
if err := notificationv1alpha1webhook.SetupContactGroupMembershipRemovalWebhooksWithManager(ctrl); err != nil {
448+
logger.Error(err, "Error setting up contactgroupmembershipremoval webhook")
449+
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
450+
}
435451

436452
projectCtrl := resourcemanagercontroller.ProjectController{
437453
ControlPlaneClient: ctrl.GetClient(),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
resources:
22
- notification.miloapis.com_emails.yaml
33
- notification.miloapis.com_emailtemplates.yaml
4+
- notification.miloapis.com_contacts.yaml
5+
- notification.miloapis.com_contactgroups.yaml
6+
- notification.miloapis.com_contactgroupmemberships.yaml
7+
- notification.miloapis.com_contactgroupmembershipremovals.yaml
48

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.18.0
7+
name: contactgroupmembershipremovals.notification.miloapis.com
8+
spec:
9+
group: notification.miloapis.com
10+
names:
11+
kind: ContactGroupMembershipRemoval
12+
listKind: ContactGroupMembershipRemovalList
13+
plural: contactgroupmembershipremovals
14+
singular: contactgroupmembershipremoval
15+
scope: Namespaced
16+
versions:
17+
- additionalPrinterColumns:
18+
- jsonPath: .spec.contactRef.name
19+
name: Contact
20+
type: string
21+
- jsonPath: .spec.contactGroupRef.name
22+
name: ContactGroup
23+
type: string
24+
- jsonPath: .status.conditions[?(@.type=='Ready')].status
25+
name: Ready
26+
type: string
27+
- jsonPath: .metadata.creationTimestamp
28+
name: Age
29+
type: date
30+
name: v1alpha1
31+
schema:
32+
openAPIV3Schema:
33+
description: |-
34+
ContactGroupMembershipRemoval is the Schema for the contactgroupmembershipremovals API.
35+
It represents a removal of a Contact from a ContactGroup, it also prevents the Contact from being added to the ContactGroup.
36+
properties:
37+
apiVersion:
38+
description: |-
39+
APIVersion defines the versioned schema of this representation of an object.
40+
Servers should convert recognized schemas to the latest internal value, and
41+
may reject unrecognized values.
42+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
43+
type: string
44+
kind:
45+
description: |-
46+
Kind is a string value representing the REST resource this object represents.
47+
Servers may infer this from the endpoint the client submits requests to.
48+
Cannot be updated.
49+
In CamelCase.
50+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
51+
type: string
52+
metadata:
53+
type: object
54+
spec:
55+
properties:
56+
contactGroupRef:
57+
description: ContactGroupRef is a reference to the ContactGroup that
58+
the Contact does not want to be a member of.
59+
properties:
60+
name:
61+
description: Name is the name of the ContactGroup being referenced.
62+
type: string
63+
namespace:
64+
description: Namespace is the namespace of the ContactGroup being
65+
referenced.
66+
type: string
67+
required:
68+
- name
69+
- namespace
70+
type: object
71+
contactRef:
72+
description: ContactRef is a reference to the Contact that prevents
73+
the Contact from being part of the ContactGroup.
74+
properties:
75+
name:
76+
description: Name is the name of the Contact being referenced.
77+
type: string
78+
namespace:
79+
description: Namespace is the namespace of the Contact being referenced.
80+
type: string
81+
required:
82+
- name
83+
- namespace
84+
type: object
85+
required:
86+
- contactGroupRef
87+
- contactRef
88+
type: object
89+
x-kubernetes-validations:
90+
- message: spec is immutable
91+
rule: self == oldSelf
92+
status:
93+
properties:
94+
conditions:
95+
default:
96+
- lastTransitionTime: "1970-01-01T00:00:00Z"
97+
message: Waiting for contact group membership removal to be created
98+
reason: CreatePending
99+
status: Unknown
100+
type: Ready
101+
description: |-
102+
Conditions represent the latest available observations of an object's current state.
103+
Standard condition is "Ready" which tracks contact group membership removal creation status.
104+
items:
105+
description: Condition contains details for one aspect of the current
106+
state of this API Resource.
107+
properties:
108+
lastTransitionTime:
109+
description: |-
110+
lastTransitionTime is the last time the condition transitioned from one status to another.
111+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
112+
format: date-time
113+
type: string
114+
message:
115+
description: |-
116+
message is a human readable message indicating details about the transition.
117+
This may be an empty string.
118+
maxLength: 32768
119+
type: string
120+
observedGeneration:
121+
description: |-
122+
observedGeneration represents the .metadata.generation that the condition was set based upon.
123+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
124+
with respect to the current state of the instance.
125+
format: int64
126+
minimum: 0
127+
type: integer
128+
reason:
129+
description: |-
130+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
131+
Producers of specific condition types may define expected values and meanings for this field,
132+
and whether the values are considered a guaranteed API.
133+
The value should be a CamelCase string.
134+
This field may not be empty.
135+
maxLength: 1024
136+
minLength: 1
137+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
138+
type: string
139+
status:
140+
description: status of the condition, one of True, False, Unknown.
141+
enum:
142+
- "True"
143+
- "False"
144+
- Unknown
145+
type: string
146+
type:
147+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
148+
maxLength: 316
149+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
150+
type: string
151+
required:
152+
- lastTransitionTime
153+
- message
154+
- reason
155+
- status
156+
- type
157+
type: object
158+
type: array
159+
type: object
160+
type: object
161+
served: true
162+
storage: true
163+
subresources:
164+
status: {}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.18.0
7+
name: contactgroupmemberships.notification.miloapis.com
8+
spec:
9+
group: notification.miloapis.com
10+
names:
11+
kind: ContactGroupMembership
12+
listKind: ContactGroupMembershipList
13+
plural: contactgroupmemberships
14+
singular: contactgroupmembership
15+
scope: Namespaced
16+
versions:
17+
- additionalPrinterColumns:
18+
- jsonPath: .spec.contactRef.name
19+
name: Contact
20+
type: string
21+
- jsonPath: .spec.contactGroupRef.name
22+
name: ContactGroup
23+
type: string
24+
- jsonPath: .status.conditions[?(@.type=='Ready')].status
25+
name: Ready
26+
type: string
27+
- jsonPath: .metadata.creationTimestamp
28+
name: Age
29+
type: date
30+
name: v1alpha1
31+
schema:
32+
openAPIV3Schema:
33+
description: |-
34+
ContactGroupMembership is the Schema for the contactgroupmemberships API.
35+
It represents a membership of a Contact in a ContactGroup.
36+
properties:
37+
apiVersion:
38+
description: |-
39+
APIVersion defines the versioned schema of this representation of an object.
40+
Servers should convert recognized schemas to the latest internal value, and
41+
may reject unrecognized values.
42+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
43+
type: string
44+
kind:
45+
description: |-
46+
Kind is a string value representing the REST resource this object represents.
47+
Servers may infer this from the endpoint the client submits requests to.
48+
Cannot be updated.
49+
In CamelCase.
50+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
51+
type: string
52+
metadata:
53+
type: object
54+
spec:
55+
description: ContactGroupMembershipSpec defines the desired state of ContactGroupMembership.
56+
properties:
57+
contactGroupRef:
58+
description: ContactGroupRef is a reference to the ContactGroup that
59+
the Contact is a member of.
60+
properties:
61+
name:
62+
description: Name is the name of the ContactGroup being referenced.
63+
type: string
64+
namespace:
65+
description: Namespace is the namespace of the ContactGroup being
66+
referenced.
67+
type: string
68+
required:
69+
- name
70+
- namespace
71+
type: object
72+
contactRef:
73+
description: ContactRef is a reference to the Contact that is a member
74+
of the ContactGroup.
75+
properties:
76+
name:
77+
description: Name is the name of the Contact being referenced.
78+
type: string
79+
namespace:
80+
description: Namespace is the namespace of the Contact being referenced.
81+
type: string
82+
required:
83+
- name
84+
- namespace
85+
type: object
86+
required:
87+
- contactGroupRef
88+
- contactRef
89+
type: object
90+
x-kubernetes-validations:
91+
- message: spec is immutable
92+
rule: self == oldSelf
93+
status:
94+
properties:
95+
conditions:
96+
default:
97+
- lastTransitionTime: "1970-01-01T00:00:00Z"
98+
message: Waiting for contact group membership to be created
99+
reason: CreatePending
100+
status: Unknown
101+
type: Ready
102+
description: |-
103+
Conditions represent the latest available observations of an object's current state.
104+
Standard condition is "Ready" which tracks contact group membership creation status and sync to the contact group membership provider.
105+
items:
106+
description: Condition contains details for one aspect of the current
107+
state of this API Resource.
108+
properties:
109+
lastTransitionTime:
110+
description: |-
111+
lastTransitionTime is the last time the condition transitioned from one status to another.
112+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
113+
format: date-time
114+
type: string
115+
message:
116+
description: |-
117+
message is a human readable message indicating details about the transition.
118+
This may be an empty string.
119+
maxLength: 32768
120+
type: string
121+
observedGeneration:
122+
description: |-
123+
observedGeneration represents the .metadata.generation that the condition was set based upon.
124+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
125+
with respect to the current state of the instance.
126+
format: int64
127+
minimum: 0
128+
type: integer
129+
reason:
130+
description: |-
131+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
132+
Producers of specific condition types may define expected values and meanings for this field,
133+
and whether the values are considered a guaranteed API.
134+
The value should be a CamelCase string.
135+
This field may not be empty.
136+
maxLength: 1024
137+
minLength: 1
138+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
139+
type: string
140+
status:
141+
description: status of the condition, one of True, False, Unknown.
142+
enum:
143+
- "True"
144+
- "False"
145+
- Unknown
146+
type: string
147+
type:
148+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
149+
maxLength: 316
150+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
151+
type: string
152+
required:
153+
- lastTransitionTime
154+
- message
155+
- reason
156+
- status
157+
- type
158+
type: object
159+
type: array
160+
providerID:
161+
description: |-
162+
ProviderID is the identifier returned by the underlying contact provider
163+
(e.g. Resend) when the membership is created in the associated audience. It is usually
164+
used to track the contact-group membership creation status (e.g. provider webhooks).
165+
type: string
166+
type: object
167+
type: object
168+
served: true
169+
storage: true
170+
subresources:
171+
status: {}

0 commit comments

Comments
 (0)