Skip to content

Commit 4ce8aaa

Browse files
authored
Merge pull request #74 from appuio/feat/console-notification
Customer notifications via console banner
2 parents 29948d2 + 45211d5 commit 4ce8aaa

20 files changed

+726
-2
lines changed

.cruft.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"name": "OpenShift4 Console",
88
"slug": "openshift4-console",
99
"parameter_key": "openshift4_console",
10-
"test_cases": "defaults custom-plugins custom-route custom-route-legacy custom-route-managed-tls custom-links custom-logo ocp-4.14",
10+
"test_cases": "defaults custom-plugins custom-route custom-route-legacy custom-route-managed-tls custom-links custom-logo ocp-4.14 notifications upgrade-notification",
1111
"add_lib": "n",
1212
"add_pp": "n",
1313
"add_golden": "y",

.github/workflows/test.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ jobs:
4040
- custom-links
4141
- custom-logo
4242
- ocp-4.14
43+
- notifications
44+
- upgrade-notification
4345
defaults:
4446
run:
4547
working-directory: ${{ env.COMPONENT_NAME }}
@@ -62,6 +64,8 @@ jobs:
6264
- custom-links
6365
- custom-logo
6466
- ocp-4.14
67+
- notifications
68+
- upgrade-notification
6569
defaults:
6670
run:
6771
working-directory: ${{ env.COMPONENT_NAME }}

Makefile.vars.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,4 @@ KUBENT_IMAGE ?= ghcr.io/doitintl/kube-no-trouble:latest
5757
KUBENT_DOCKER ?= $(DOCKER_CMD) $(DOCKER_ARGS) $(root_volume) --entrypoint=/app/kubent $(KUBENT_IMAGE)
5858

5959
instance ?= defaults
60-
test_instances = tests/defaults.yml tests/custom-plugins.yml tests/custom-route.yml tests/custom-route-legacy.yml tests/custom-route-managed-tls.yml tests/custom-links.yml tests/custom-logo.yml tests/ocp-4.14.yml
60+
test_instances = tests/defaults.yml tests/custom-plugins.yml tests/custom-route.yml tests/custom-route-legacy.yml tests/custom-route-managed-tls.yml tests/custom-links.yml tests/custom-logo.yml tests/ocp-4.14.yml tests/notifications.yml tests/upgrade-notification.yml

class/defaults.yml

+5
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ parameters:
2626

2727
console_links: {}
2828
custom_logo: {}
29+
30+
notifications: {}
31+
upgrade_notification:
32+
enabled: false
33+
notification: {}

component/main.jsonnet

+6
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ local consoleRoutePatch =
172172

173173
local tls = import 'tls.libsonnet';
174174

175+
local notifications = import 'notifications.libsonnet';
176+
175177
{
176178
'00_namespace': kube.Namespace(params.namespace) {
177179
metadata+: {
@@ -205,4 +207,8 @@ local tls = import 'tls.libsonnet';
205207
faviconRoute,
206208
[if consoleRoutePatch != null then '20_ingress_config_patch']:
207209
consoleRoutePatch,
210+
[if std.length(notifications.rbac) > 0 then '30_notification_rbac']:
211+
notifications.rbac,
212+
[if std.length(notifications.notifications) > 0 then '30_notifications']: notifications.notifications,
213+
[if std.length(notifications.upgrade_notification) > 0 then '31_upgrade_notification']: notifications.upgrade_notification,
208214
}

component/notifications.libsonnet

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
local com = import 'lib/commodore.libjsonnet';
2+
local kap = import 'lib/kapitan.libjsonnet';
3+
local kube = import 'lib/kube.libjsonnet';
4+
5+
local inv = kap.inventory();
6+
local params = inv.parameters.openshift4_console;
7+
8+
local namespace = {
9+
metadata+: {
10+
namespace: params.namespace,
11+
},
12+
};
13+
14+
local makeConsoleNotification(name, args) =
15+
kube._Object('console.openshift.io/v1', 'ConsoleNotification', name) {
16+
metadata+: {
17+
labels+: {
18+
'appuio.io/notification': 'true',
19+
},
20+
},
21+
spec: std.prune(
22+
{
23+
text: args.text,
24+
location: std.get(args, 'location', 'BannerTop'),
25+
color: std.get(args, 'color', '#fff'),
26+
backgroundColor: std.get(args, 'backgroundColor', '#2596be'),
27+
link: std.get(args, 'link'),
28+
},
29+
),
30+
};
31+
32+
local consoleNotifications = [
33+
makeConsoleNotification(name, params.notifications[name])
34+
for name in std.objectFields(params.notifications)
35+
if params.notifications[name] != null
36+
];
37+
38+
local nextChannelOverlay() =
39+
local overlays = inv.parameters.openshift_upgrade_controller.cluster_version.overlays;
40+
local channelOverlays = {
41+
[date]: overlays[date].spec.channel
42+
for date in std.objectFields(overlays)
43+
if std.objectHas(overlays[date].spec, 'channel')
44+
&& std.split(overlays[date].spec.channel, '.')[1] > params.openshift_version.Minor
45+
};
46+
local date = if std.length(channelOverlays) > 0 then
47+
std.sort(std.objectFields(channelOverlays))[0];
48+
if date != null then
49+
{
50+
date: date,
51+
channel: channelOverlays[date],
52+
version: std.split(channelOverlays[date], '-')[1],
53+
};
54+
55+
local upgradeControllerNS = {
56+
metadata+: {
57+
namespace: inv.parameters.openshift_upgrade_controller.namespace,
58+
},
59+
};
60+
61+
local notificationRBAC =
62+
local argocd_sa = kube.ServiceAccount('notification-manager') + namespace;
63+
local upgrade_sa = argocd_sa + upgradeControllerNS;
64+
local cluster_role = kube.ClusterRole('appuio:upgrade-notification-editor') {
65+
rules: [
66+
{
67+
apiGroups: [ 'console.openshift.io' ],
68+
resources: [ 'consolenotifications' ],
69+
verbs: [ '*' ],
70+
},
71+
{
72+
apiGroups: [ 'managedupgrade.appuio.io' ],
73+
resources: [ 'upgradeconfigs' ],
74+
verbs: [ 'get', 'list' ],
75+
},
76+
// needed so that `oc version` can get the OCP server version
77+
{
78+
apiGroups: [ 'config.openshift.io' ],
79+
resources: [ 'clusterversions' ],
80+
verbs: [ 'get', 'list' ],
81+
},
82+
{
83+
apiGroups: [ '' ],
84+
resources: [ 'configmaps' ],
85+
resourceNames: [ 'upgrade-notification-template' ],
86+
verbs: [ '*' ],
87+
},
88+
],
89+
};
90+
local cluster_role_binding =
91+
kube.ClusterRoleBinding('appuio:upgrade-notification-manager') {
92+
subjects_: [ argocd_sa, upgrade_sa ],
93+
roleRef_: cluster_role,
94+
};
95+
{
96+
argocd_sa: argocd_sa,
97+
upgrade_sa: upgrade_sa,
98+
cluster_role: cluster_role,
99+
cluster_role_binding: cluster_role_binding,
100+
};
101+
102+
local createUpgradeNotification(overlay) =
103+
[
104+
kube.ConfigMap('upgrade-notification-template') + namespace {
105+
data: {
106+
'upgrade.yaml': std.manifestYamlDoc(
107+
makeConsoleNotification('upgrade-%s' % overlay.version, params.upgrade_notification.notification) {
108+
metadata+: {
109+
labels+: {
110+
'appuio.io/ocp-version': overlay.version,
111+
},
112+
},
113+
},
114+
),
115+
},
116+
},
117+
118+
kube.ConfigMap('console-notification-script') {
119+
metadata+: {
120+
namespace: params.namespace,
121+
},
122+
data: {
123+
'create-console-notification.sh': (importstr 'scripts/create-console-notification.sh'),
124+
},
125+
},
126+
127+
kube.Job('create-upgrade-notification') + namespace {
128+
metadata+: {
129+
annotations+: {
130+
'argocd.argoproj.io/hook': 'PostSync',
131+
'argocd.argoproj.io/hook-delete-policy': 'BeforeHookCreation',
132+
},
133+
},
134+
spec+: {
135+
template+: {
136+
spec+: {
137+
containers_+: {
138+
notification: kube.Container('notification') {
139+
image: '%(registry)s/%(repository)s:%(tag)s' % params.images.oc,
140+
name: 'create-console-notification',
141+
workingDir: '/export',
142+
command: [ '/scripts/create-console-notification.sh' ],
143+
env_+: {
144+
OVERLAY_DATE: overlay.date,
145+
OVERLAY_CHANNEL: overlay.channel,
146+
OVERLAY_VERSION: overlay.version,
147+
OVERLAY_VERSION_MINOR: std.split(overlay.version, '.')[1],
148+
},
149+
volumeMounts_+: {
150+
'upgrade-notification-template': {
151+
mountPath: 'export/template',
152+
readOnly: true,
153+
},
154+
export: {
155+
mountPath: '/export',
156+
},
157+
scripts: {
158+
mountPath: '/scripts',
159+
},
160+
},
161+
},
162+
},
163+
volumes_+: {
164+
'upgrade-notification-template': {
165+
configMap: {
166+
name: 'upgrade-notification-template',
167+
defaultMode: std.parseOctal('0550'),
168+
},
169+
},
170+
export: {
171+
emptyDir: {},
172+
},
173+
scripts: {
174+
configMap: {
175+
name: 'console-notification-script',
176+
defaultMode: std.parseOctal('0550'),
177+
},
178+
},
179+
},
180+
serviceAccountName: notificationRBAC.argocd_sa.metadata.name,
181+
},
182+
},
183+
},
184+
},
185+
];
186+
187+
188+
local hookScript = kube.ConfigMap('cleanup-upgrade-notification') + upgradeControllerNS {
189+
data: {
190+
'cleanup-upgrade-notification.sh': (importstr 'scripts/cleanup-upgrade-notification.sh'),
191+
},
192+
};
193+
194+
local ujh = kube._Object('managedupgrade.appuio.io/v1beta1', 'UpgradeJobHook', 'cleanup-upgrade-notification') + upgradeControllerNS {
195+
spec+: {
196+
selector: {
197+
matchLabels: {
198+
'appuio-managed-upgrade': 'true',
199+
},
200+
},
201+
events: [
202+
'Finish',
203+
],
204+
template+: {
205+
spec+: {
206+
template+: {
207+
spec+: {
208+
restartPolicy: 'Never',
209+
containers: [
210+
kube.Container('cleanup') {
211+
image: '%(registry)s/%(repository)s:%(tag)s' % params.images.oc,
212+
command: [ '/usr/local/bin/cleanup' ],
213+
volumeMounts_+: {
214+
scripts: {
215+
mountPath: '/usr/local/bin/cleanup',
216+
readOnly: true,
217+
subPath: 'cleanup-upgrade-notification.sh',
218+
},
219+
},
220+
},
221+
],
222+
serviceAccountName: notificationRBAC.upgrade_sa.metadata.name,
223+
volumes: [
224+
{
225+
name: 'scripts',
226+
configMap: {
227+
name: hookScript.metadata.name,
228+
defaultMode: std.parseOctal('0550'),
229+
},
230+
},
231+
],
232+
},
233+
},
234+
},
235+
},
236+
},
237+
};
238+
239+
240+
local upgradeNotification = if params.upgrade_notification.enabled then
241+
local channelOverlay = nextChannelOverlay();
242+
local notification = if channelOverlay != null then
243+
createUpgradeNotification(channelOverlay)
244+
else [];
245+
notification + [
246+
hookScript,
247+
ujh,
248+
] else [];
249+
250+
{
251+
rbac: if params.upgrade_notification.enabled then
252+
std.objectValues(notificationRBAC) else [],
253+
notifications: consoleNotifications,
254+
upgrade_notification: upgradeNotification,
255+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
set -exo pipefail
3+
4+
OCP_MINOR=$(echo $JOB_spec_desiredVersion_version | jq -r 'split(".") | .[0:2] | join(".")')
5+
echo $OCP_MINOR
6+
echo "Deleting upgrade console notification"
7+
kubectl delete consolenotifications -l appuio.io/ocp-version="$OCP_MINOR"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
echo "OVERLAY_DATE: $OVERLAY_DATE"
5+
echo "OVERLAY_CHANNEL: $OVERLAY_CHANNEL"
6+
echo "OVERLAY_VERSION: $OVERLAY_VERSION"
7+
echo "OVERLAY_VERSION_MINOR: $OVERLAY_VERSION_MINOR"
8+
9+
NEXT_MAINTENANCE=$(kubectl -n appuio-openshift-upgrade-controller get upgradeconfigs -oyaml | yq '[.items[].status.nextPossibleSchedules[].time | from_yaml | select(. > env(OVERLAY_DATE))][0] | tz("Europe/Zurich") | format_datetime("02.01.2006 15:04")')
10+
test -n "${NEXT_MAINTENANCE:-}" || (echo "No valid maintenance window found" && exit 1)
11+
echo "NEXT_MAINTENANCE: $NEXT_MAINTENANCE"
12+
export NEXT_MAINTENANCE
13+
14+
yq '(.. | select(tag == "!!str")) |= envsubst' template/upgrade.yaml > notification.yaml
15+
cat notification.yaml
16+
17+
CURRENT_MINOR=$(oc version -oyaml | yq '.openshiftVersion' | cut -d'.' -f2)
18+
if (( OVERLAY_VERSION_MINOR > CURRENT_MINOR )); then
19+
echo "Creating console notification:"
20+
kubectl apply -f notification.yaml
21+
else
22+
echo "Current OpenShift minor version matches channel overlay version."
23+
fi

0 commit comments

Comments
 (0)