@red-hat-developer-hub/e2e-test-utils 1.1.17 → 1.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,13 @@ export declare class KeycloakHelper {
46
46
  * Create a user in a realm with optional group membership
47
47
  */
48
48
  createUser(realm: string, config: KeycloakUserConfig): Promise<void>;
49
+ /**
50
+ * Create users and groups in a realm.
51
+ */
52
+ createUsersAndGroups(realm: string, options: {
53
+ users?: KeycloakUserConfig[];
54
+ groups?: KeycloakGroupConfig[];
55
+ }): Promise<void>;
49
56
  /**
50
57
  * Get all users in a realm
51
58
  */
@@ -66,6 +73,13 @@ export declare class KeycloakHelper {
66
73
  * Delete a group from a realm
67
74
  */
68
75
  deleteGroup(realm: string, groupName: string): Promise<void>;
76
+ /**
77
+ * Delete users and groups from a realm.
78
+ */
79
+ deleteUsersAndGroups(realm: string, options: {
80
+ users?: Array<KeycloakUserConfig | string>;
81
+ groups?: Array<KeycloakGroupConfig | string>;
82
+ }): Promise<void>;
69
83
  /**
70
84
  * Delete a realm
71
85
  */
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/keycloak/deployment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAY1E,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAM;IACzB,KAAK,EAAE,MAAM,CAAM;IACnB,QAAQ,EAAE,MAAM,CAAM;IACtB,YAAY,EAAE,MAAM,CAAM;IACjC,OAAO,CAAC,YAAY,CAAoC;gBAE5C,OAAO,GAAE,yBAA8B;IAInD;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa7B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAUnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;QAC/B,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9D;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7D;;OAEG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5E;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C1E;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAe5D;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAQ9D;;OAEG;IACG,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAejC;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B;;OAEG;IACG,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,OAAO,CAAC,sBAAsB;YAahB,eAAe;YAWf,YAAY;IAwBpB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;YAO3B,gBAAgB;YAoBhB,sBAAsB;YActB,kBAAkB;YAQlB,0BAA0B;YAmD1B,eAAe;IAqB7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,IAAI;CAGb"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/keycloak/deployment.ts"],"names":[],"mappings":"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"}
@@ -220,6 +220,19 @@ export class KeycloakHelper {
220
220
  }
221
221
  }
222
222
  }
223
+ /**
224
+ * Create users and groups in a realm.
225
+ */
226
+ async createUsersAndGroups(realm, options) {
227
+ await this._ensureAdminClient();
228
+ const { groups = [], users = [] } = options;
229
+ for (const group of groups) {
230
+ await this.createGroup(realm, group);
231
+ }
232
+ for (const user of users) {
233
+ await this.createUser(realm, user);
234
+ }
235
+ }
223
236
  /**
224
237
  * Get all users in a realm
225
238
  */
@@ -265,6 +278,9 @@ export class KeycloakHelper {
265
278
  * Delete a user from a realm
266
279
  */
267
280
  async deleteUser(realm, username) {
281
+ if (DEFAULT_USERS.some((u) => u.username === username)) {
282
+ throw new Error(`Deleting default Keycloak user "${username}" is not permitted.`);
283
+ }
268
284
  await this._ensureAdminClient();
269
285
  this._adminClient.setConfig({ realmName: realm });
270
286
  const users = await this._adminClient.users.find({ username });
@@ -277,6 +293,9 @@ export class KeycloakHelper {
277
293
  * Delete a group from a realm
278
294
  */
279
295
  async deleteGroup(realm, groupName) {
296
+ if (DEFAULT_GROUPS.some((g) => g.name === groupName)) {
297
+ throw new Error(`Deleting default Keycloak group "${groupName}" is not permitted.`);
298
+ }
280
299
  await this._ensureAdminClient();
281
300
  this._adminClient.setConfig({ realmName: realm });
282
301
  const groups = await this._adminClient.groups.find({ search: groupName });
@@ -286,6 +305,21 @@ export class KeycloakHelper {
286
305
  this._log(`Deleted group: ${groupName}`);
287
306
  }
288
307
  }
308
+ /**
309
+ * Delete users and groups from a realm.
310
+ */
311
+ async deleteUsersAndGroups(realm, options) {
312
+ await this._ensureAdminClient();
313
+ const { groups = [], users = [] } = options;
314
+ const usernames = users.map((u) => typeof u === "string" ? u : u.username);
315
+ const groupNames = groups.map((g) => (typeof g === "string" ? g : g.name));
316
+ for (const username of usernames) {
317
+ await this.deleteUser(realm, username);
318
+ }
319
+ for (const groupName of groupNames) {
320
+ await this.deleteGroup(realm, groupName);
321
+ }
322
+ }
289
323
  /**
290
324
  * Delete a realm
291
325
  */
@@ -0,0 +1,3 @@
1
+ export declare function installOrchestrator(namespace?: string): Promise<void>;
2
+ export default installOrchestrator;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/deployment/orchestrator/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,mBAAmB,CAAC,SAAS,SAAiB,iBAEnE;AAED,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { resolve } from "path";
2
+ import { $ } from "../../utils/index.js";
3
+ const scriptPath = resolve(import.meta.dirname, "install-orchestrator.sh");
4
+ export async function installOrchestrator(namespace = "orchestrator") {
5
+ await $ `bash ${scriptPath} ${namespace}`;
6
+ }
7
+ export default installOrchestrator;
@@ -0,0 +1,477 @@
1
+ #!/bin/bash
2
+ #
3
+ # Standalone script to install the orchestrator (Serverless Logic / SonataFlow)
4
+ # on OpenShift.
5
+ #
6
+ # Usage: ./install-orchestrator.sh [namespace]
7
+ # Default namespace: orchestrator
8
+ #
9
+
10
+ set -e
11
+
12
+ export NAME_SPACE="${1:-${NAME_SPACE:-orchestrator}}"
13
+
14
+ LOWER_CASE_CLASS='[:lower:]'
15
+ UPPER_CASE_CLASS='[:upper:]'
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Logging
19
+ # ---------------------------------------------------------------------------
20
+ if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then
21
+ : "${LOG_NO_COLOR:=false}"
22
+ else
23
+ : "${LOG_NO_COLOR:=true}"
24
+ fi
25
+ : "${LOG_LEVEL:=INFO}"
26
+
27
+ log::timestamp() {
28
+ echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
29
+ return 0
30
+ }
31
+ log::level_value() {
32
+ local input="$1"
33
+ local level
34
+ level="$(echo "$input" | tr "$LOWER_CASE_CLASS" "$UPPER_CASE_CLASS")"
35
+ case "${level}" in DEBUG) echo 0 ;; INFO) echo 1 ;; WARN|WARNING) echo 2 ;; ERROR|ERR) echo 3 ;; *) echo 1 ;; esac
36
+ return 0;
37
+ }
38
+ log::should_log() {
39
+ local input requested_level config_level
40
+ input="$1"
41
+ requested_level="$(echo "$input" | tr "$LOWER_CASE_CLASS" "$UPPER_CASE_CLASS")"
42
+ config_level="$(echo "${LOG_LEVEL}" | tr "$LOWER_CASE_CLASS" "$UPPER_CASE_CLASS")"
43
+
44
+ [[ "$(log::level_value "${requested_level}")" -ge "$(log::level_value "${config_level}")" ]]
45
+ return $?
46
+ }
47
+ log::reset_code() {
48
+ [[ "${LOG_NO_COLOR}" == "true" ]] && printf '' || printf '\033[0m'
49
+ return 0;
50
+ }
51
+ log::color_for_level() {
52
+ [[ "${LOG_NO_COLOR}" == "true" ]] && { printf ''; return 0; }
53
+ local level input
54
+ input="$1"
55
+ level="$(echo "$input" | tr "$LOWER_CASE_CLASS" "$UPPER_CASE_CLASS")"
56
+ case "${level}" in
57
+ DEBUG) printf '\033[36m' ;; INFO) printf '\033[34m' ;; WARN|WARNING) printf '\033[33m' ;;
58
+ ERROR|ERR) printf '\033[31m' ;; SUCCESS) printf '\033[32m' ;; SECTION) printf '\033[35m\033[1m' ;;
59
+ *) printf '\033[37m' ;;
60
+ esac
61
+ }
62
+ log::icon_for_level() {
63
+ local level input
64
+ input="$1"
65
+ level="$(echo "$input" | tr "$LOWER_CASE_CLASS" "$UPPER_CASE_CLASS")"
66
+ case "${level}" in DEBUG) printf '🐞' ;; INFO) printf 'ℹ' ;; WARN|WARNING) printf '⚠' ;; ERROR|ERR) printf '❌' ;; SUCCESS) printf '✓' ;; *) printf '-' ;; esac
67
+ return 0
68
+ }
69
+ log::emit_line() {
70
+ local level="$1" icon="$2" line="$3" color reset timestamp
71
+ log::should_log "${level}" || return 0
72
+ timestamp="$(log::timestamp)"
73
+ color="$(log::color_for_level "${level}")"
74
+ reset="$(log::reset_code)"
75
+ printf '%s[%s] %s %s%s\n' "${color}" "${timestamp}" "${icon}" "${line}" "${reset}" >&2
76
+ }
77
+ log::emit() {
78
+ local level="$1"; shift
79
+ local icon message; icon="$(log::icon_for_level "${level}")"; message="${*:-}"
80
+ [[ -z "${message}" ]] && return 0
81
+ while IFS= read -r line; do log::emit_line "${level}" "${icon}" "${line}"; done <<< "${message}"
82
+ }
83
+ log::debug() {
84
+ log::emit "DEBUG" "$@"
85
+ return 0
86
+ }
87
+ log::info() {
88
+ log::emit "INFO" "$@"
89
+ return 0
90
+ }
91
+ log::warn() {
92
+ log::emit "WARN" "$@"
93
+ return 0
94
+ }
95
+ log::error() {
96
+ log::emit "ERROR" "$@"
97
+ return 0
98
+ }
99
+ log::success() {
100
+ log::emit "SUCCESS" "$@"
101
+ return 0
102
+ }
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Operator subscription and status
106
+ # ---------------------------------------------------------------------------
107
+ install_subscription() {
108
+ local name=$1 namespace=$2 channel=$3 package=$4 source_name=$5 source_namespace=$6
109
+ oc apply -f - << EOD
110
+ apiVersion: operators.coreos.com/v1alpha1
111
+ kind: Subscription
112
+ metadata:
113
+ name: $name
114
+ namespace: $namespace
115
+ spec:
116
+ channel: $channel
117
+ installPlanApproval: Automatic
118
+ name: $package
119
+ source: $source_name
120
+ sourceNamespace: $source_namespace
121
+ EOD
122
+ return 0
123
+ }
124
+
125
+ check_operator_status() {
126
+ local timeout=${1:-300} namespace=$2 operator_name=$3 expected_status=${4:-Succeeded}
127
+ log::info "Checking operator '${operator_name}' in '${namespace}' (timeout ${timeout}s, expected: ${expected_status})"
128
+ timeout "${timeout}" bash -c "
129
+ while true; do
130
+ CURRENT_PHASE=\$(oc get csv -n '${namespace}' -o jsonpath='{.items[?(@.spec.displayName==\"${operator_name}\")].status.phase}')
131
+ echo \"[check_operator_status] Phase: \${CURRENT_PHASE}\" >&2
132
+ [[ \"\${CURRENT_PHASE}\" == \"${expected_status}\" ]] && echo \"[check_operator_status] Operator reached ${expected_status}\" >&2 && break
133
+ sleep 10
134
+ done
135
+ " || { log::error "Operator did not reach ${expected_status} in time."; return 1; }
136
+ }
137
+
138
+ install_serverless_logic_ocp_operator() {
139
+ install_subscription logic-operator-rhel8 openshift-operators alpha logic-operator-rhel8 redhat-operators openshift-marketplace
140
+ return 0
141
+ }
142
+ waitfor_serverless_logic_ocp_operator() {
143
+ check_operator_status 500 openshift-operators "OpenShift Serverless Logic Operator (Alpha)" Succeeded
144
+ return 0
145
+ }
146
+
147
+ install_serverless_ocp_operator() {
148
+ install_subscription serverless-operator openshift-operators stable serverless-operator redhat-operators openshift-marketplace
149
+ return 0
150
+ }
151
+ waitfor_serverless_ocp_operator() {
152
+ check_operator_status 300 openshift-operators "Red Hat OpenShift Serverless" Succeeded
153
+ return 0
154
+ }
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Namespace
158
+ # ---------------------------------------------------------------------------
159
+ force_delete_namespace() {
160
+ local project=$1 timeout_seconds=${2:-120} elapsed=0 sleep_interval=2
161
+ log::warn "Force deleting namespace ${project}"
162
+ oc get namespace "$project" -o json | jq '.spec = {"finalizers":[]}' | oc replace --raw "/api/v1/namespaces/$project/finalize" -f -
163
+ while oc get namespace "$project" &>/dev/null; do
164
+ [[ $elapsed -ge $timeout_seconds ]] && { log::warn "Timeout deleting ${project}"; return 1; }
165
+ sleep $sleep_interval
166
+ elapsed=$((elapsed + sleep_interval))
167
+ done
168
+ log::success "Namespace '${project}' deleted."
169
+ }
170
+
171
+ delete_namespace() {
172
+ local project=$1
173
+ if oc get namespace "$project" &>/dev/null; then
174
+ log::warn "Deleting namespace ${project}..."
175
+ oc delete namespace "$project" --grace-period=0 --force || true
176
+ if oc get namespace "$project" -o jsonpath='{.status.phase}' 2>/dev/null | grep -q Terminating; then
177
+ force_delete_namespace "$project"
178
+ fi
179
+ fi
180
+ return 0
181
+ }
182
+
183
+ configure_namespace() {
184
+ local project=$1
185
+ if oc get namespace "$project" &>/dev/null; then
186
+ log::info "Namespace ${project} already exists, reusing it."
187
+ else
188
+ log::info "Creating namespace: ${project}"
189
+ oc create namespace "${project}" || { log::error "Failed to create namespace ${project}"; exit 1; }
190
+ fi
191
+ oc config set-context --current --namespace="${project}" || { log::error "Failed to set context"; exit 1; }
192
+ log::info "Namespace ${project} is ready."
193
+ return 0
194
+ }
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # Deployment wait
198
+ # ---------------------------------------------------------------------------
199
+ wait_for_deployment() {
200
+ local namespace=$1 resource_name=$2 timeout_minutes=${3:-5} check_interval=${4:-10}
201
+ [[ -z "$namespace" || -z "$resource_name" ]] && { log::error "wait_for_deployment: namespace and resource_name required"; return 1; }
202
+ local max_attempts=$((timeout_minutes * 60 / check_interval))
203
+ log::info "Waiting for '$resource_name' in '$namespace' (timeout ${timeout_minutes}m)..."
204
+ for ((i = 1; i <= max_attempts; i++)); do
205
+ local pod_name
206
+ pod_name=$(oc get pods -n "$namespace" 2>/dev/null | grep "$resource_name" | awk '{print $1}' | head -n 1)
207
+ if [[ -n "$pod_name" ]]; then
208
+ local is_ready
209
+ is_ready=$(oc get pod "$pod_name" -n "$namespace" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)
210
+ if [[ "$is_ready" == "True" ]] && oc get pod "$pod_name" -n "$namespace" 2>/dev/null | grep -q Running; then
211
+ log::success "Pod '$pod_name' is ready"
212
+ return 0
213
+ fi
214
+ fi
215
+ sleep "$check_interval"
216
+ done
217
+ log::error "Timeout waiting for $resource_name"
218
+ return 1
219
+ }
220
+
221
+ # ---------------------------------------------------------------------------
222
+ # PostgreSQL (simple deployment for orchestrator)
223
+ # ---------------------------------------------------------------------------
224
+ create_simple_postgres_deployment() {
225
+ local namespace=$1 postgres_name="backstage-psql"
226
+ if oc get deployment "$postgres_name" -n "$namespace" &>/dev/null; then
227
+ log::info "PostgreSQL '$postgres_name' already exists"
228
+ return 0
229
+ fi
230
+ log::info "Creating PostgreSQL '$postgres_name' in '$namespace'"
231
+ oc create secret generic "${postgres_name}-secret" -n "$namespace" \
232
+ --from-literal=POSTGRESQL_USER=postgres \
233
+ --from-literal=POSTGRESQL_PASSWORD=postgres \
234
+ --from-literal=POSTGRESQL_DATABASE=postgres \
235
+ --from-literal=POSTGRES_USER=postgres \
236
+ --from-literal=POSTGRES_PASSWORD=postgres \
237
+ --from-literal=POSTGRES_DB=postgres \
238
+ --dry-run=client -o yaml | oc apply -f - -n "$namespace" || true
239
+
240
+ oc apply -f - -n "$namespace" << EOF
241
+ apiVersion: v1
242
+ kind: PersistentVolumeClaim
243
+ metadata:
244
+ name: ${postgres_name}-pvc
245
+ namespace: ${namespace}
246
+ spec:
247
+ accessModes: [ReadWriteOnce]
248
+ resources: { requests: { storage: 1Gi } }
249
+ EOF
250
+
251
+ oc apply -f - -n "$namespace" << EOF
252
+ apiVersion: apps/v1
253
+ kind: StatefulSet
254
+ metadata:
255
+ name: ${postgres_name}
256
+ namespace: ${namespace}
257
+ spec:
258
+ serviceName: ${postgres_name}
259
+ replicas: 1
260
+ selector: { matchLabels: { app: ${postgres_name} } }
261
+ template:
262
+ metadata: { labels: { app: ${postgres_name} } }
263
+ spec:
264
+ containers:
265
+ - name: postgres
266
+ image: registry.redhat.io/rhel9/postgresql-15:latest
267
+ env:
268
+ - name: POSTGRESQL_USER
269
+ valueFrom: { secretKeyRef: { name: ${postgres_name}-secret, key: POSTGRESQL_USER } }
270
+ - name: POSTGRESQL_PASSWORD
271
+ valueFrom: { secretKeyRef: { name: ${postgres_name}-secret, key: POSTGRESQL_PASSWORD } }
272
+ - name: POSTGRESQL_DATABASE
273
+ valueFrom: { secretKeyRef: { name: ${postgres_name}-secret, key: POSTGRESQL_DATABASE } }
274
+ ports: [ { containerPort: 5432, name: postgres } ]
275
+ volumeMounts: [ { name: postgres-data, mountPath: /var/lib/pgsql/data } ]
276
+ livenessProbe:
277
+ exec: { command: [ /usr/libexec/check-container, --live ] }
278
+ initialDelaySeconds: 120
279
+ periodSeconds: 10
280
+ readinessProbe:
281
+ exec: { command: [ /usr/libexec/check-container ] }
282
+ initialDelaySeconds: 5
283
+ periodSeconds: 10
284
+ volumes: [ { name: postgres-data, persistentVolumeClaim: { claimName: ${postgres_name}-pvc } } ]
285
+ EOF
286
+
287
+ oc apply -f - -n "$namespace" << EOF
288
+ apiVersion: v1
289
+ kind: Service
290
+ metadata:
291
+ name: ${postgres_name}
292
+ namespace: ${namespace}
293
+ spec:
294
+ selector: { app: ${postgres_name} }
295
+ ports: [ { name: postgres, port: 5432, targetPort: 5432 } ]
296
+ type: ClusterIP
297
+ EOF
298
+
299
+ log::info "Waiting for PostgreSQL StatefulSet..."
300
+ oc wait statefulset "$postgres_name" -n "$namespace" --for=jsonpath='{.status.readyReplicas}'=1 --timeout=300s || true
301
+ sleep 5
302
+ oc exec -n "$namespace" statefulset/"$postgres_name" -- psql -U postgres -c "CREATE DATABASE backstage_plugin_orchestrator;" 2>/dev/null || log::warn "Orchestrator DB may already exist"
303
+ log::success "PostgreSQL deployment created."
304
+ }
305
+
306
+ # ---------------------------------------------------------------------------
307
+ # SonataFlow platform
308
+ # ---------------------------------------------------------------------------
309
+ create_sonataflow_platform() {
310
+ local namespace=$1 postgres_secret_name=$2 postgres_service_name=$3
311
+ if ! oc get crd sonataflowplatforms.sonataflow.org &>/dev/null && ! oc get crd sonataflowplatform.sonataflow.org &>/dev/null; then
312
+ log::error "SonataFlowPlatform CRD not found. Install Serverless Logic Operator first."
313
+ return 1
314
+ fi
315
+ if oc get sonataflowplatform sonataflow-platform -n "$namespace" &>/dev/null || oc get sfp sonataflow-platform -n "$namespace" &>/dev/null; then
316
+ log::info "SonataFlowPlatform already exists"
317
+ return 0
318
+ fi
319
+ log::info "Creating SonataFlowPlatform in '$namespace'"
320
+ oc apply -f - -n "$namespace" << EOF
321
+ apiVersion: sonataflow.org/v1alpha08
322
+ kind: SonataFlowPlatform
323
+ metadata:
324
+ name: sonataflow-platform
325
+ namespace: ${namespace}
326
+ spec:
327
+ services:
328
+ dataIndex:
329
+ persistence:
330
+ postgresql:
331
+ secretRef: { name: ${postgres_secret_name}, userKey: POSTGRES_USER, passwordKey: POSTGRES_PASSWORD }
332
+ serviceRef: { name: ${postgres_service_name}, namespace: ${namespace}, port: 5432, databaseName: backstage_plugin_orchestrator }
333
+ jobService:
334
+ persistence:
335
+ postgresql:
336
+ secretRef: { name: ${postgres_secret_name}, userKey: POSTGRES_USER, passwordKey: POSTGRES_PASSWORD }
337
+ serviceRef: { name: ${postgres_service_name}, namespace: ${namespace}, port: 5432, databaseName: backstage_plugin_orchestrator }
338
+ EOF
339
+ local attempt=0 max_attempts=60
340
+ while [[ $attempt -lt $max_attempts ]]; do
341
+ if oc get deployment sonataflow-platform-data-index-service -n "$namespace" &>/dev/null && \
342
+ oc get deployment sonataflow-platform-jobs-service -n "$namespace" &>/dev/null; then
343
+ log::success "SonataFlowPlatform services created"
344
+ wait_for_deployment "$namespace" sonataflow-platform-data-index-service 20 || true
345
+ wait_for_deployment "$namespace" sonataflow-platform-jobs-service 20 || true
346
+ log::success "SonataFlowPlatform ready."
347
+ return 0
348
+ fi
349
+ attempt=$((attempt + 1))
350
+ [[ $((attempt % 10)) -eq 0 ]] && log::info "Waiting for SonataFlowPlatform... ($attempt/$max_attempts)"
351
+ sleep 5
352
+ done
353
+ log::warn "SonataFlowPlatform services did not appear in time."
354
+ }
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # Orchestrator connection info
358
+ # ---------------------------------------------------------------------------
359
+ print_orchestrator_connection_info() {
360
+ local namespace=$1
361
+ local data_index_service="sonataflow-platform-data-index-service"
362
+ local service_url="http://${data_index_service}.${namespace}.svc.cluster.local"
363
+ log::info "=========================================="
364
+ log::info "Orchestrator Plugin Connection Information"
365
+ log::info "=========================================="
366
+ log::info "Namespace: ${namespace}"
367
+ log::info "Internal URL for Orchestrator Backend Plugin: ${service_url}"
368
+ log::info "dynamic-plugins.yaml: pluginConfig.orchestrator.dataIndexService.url: ${service_url}"
369
+ if oc get svc "${data_index_service}" -n "${namespace}" &>/dev/null; then
370
+ local port; port=$(oc get svc "${data_index_service}" -n "${namespace}" -o jsonpath='{.spec.ports[0].port}' 2>/dev/null || echo "8080")
371
+ log::info "Service: ${data_index_service}, port: ${port}"
372
+ else
373
+ log::warn "Service '${data_index_service}' not found yet."
374
+ fi
375
+ log::info "=========================================="
376
+ return 0
377
+ }
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # Wait for SonataFlow CRDs
381
+ # ---------------------------------------------------------------------------
382
+ wait_for_sonataflow_crds() {
383
+ log::info "Waiting for SonataFlow CRDs..."
384
+ local attempt=0 max_attempts=60
385
+ while [[ $attempt -lt $max_attempts ]]; do
386
+ if oc get crd sonataflows.sonataflow.org &>/dev/null; then
387
+ log::success "SonataFlow CRD is available."
388
+ return 0
389
+ fi
390
+ attempt=$((attempt + 1))
391
+ [[ $((attempt % 6)) -eq 0 ]] && log::info "Waiting for sonataflows.sonataflow.org... ($attempt/$max_attempts)"
392
+ sleep 5
393
+ done
394
+ log::error "Timed out waiting for SonataFlow CRD."
395
+ return 1
396
+ }
397
+
398
+ # ---------------------------------------------------------------------------
399
+ # Deploy orchestrator workflows (operator path: git clone + helm greeting)
400
+ # Uses local yaml/ if present, otherwise clones repo.
401
+ # ---------------------------------------------------------------------------
402
+ deploy_orchestrator_workflows_operator() {
403
+ local namespace=$1
404
+
405
+ # PostgreSQL
406
+ if ! oc get statefulset backstage-psql -n "$namespace" &>/dev/null && ! oc get deployment backstage-psql -n "$namespace" &>/dev/null; then
407
+ log::info "Creating simple PostgreSQL deployment..."
408
+ create_simple_postgres_deployment "$namespace"
409
+ else
410
+ log::info "PostgreSQL found, waiting for ready..."
411
+ if oc get statefulset backstage-psql -n "$namespace" &>/dev/null; then
412
+ oc wait statefulset backstage-psql -n "$namespace" --for=jsonpath='{.status.readyReplicas}'=1 --timeout=300s || true
413
+ else
414
+ wait_for_deployment "$namespace" backstage-psql 15 || true
415
+ fi
416
+ fi
417
+
418
+ local psql_secret_name psql_svc_name
419
+ psql_secret_name=$(oc get secrets -n "$namespace" -o name 2>/dev/null | grep "backstage-psql" | grep "secret" | head -1 | sed 's|secret\/||')
420
+ psql_svc_name='backstage-psql'
421
+
422
+ log::info "PostgreSQL secret: $psql_secret_name, service: $psql_svc_name"
423
+
424
+ if ! oc get sonataflowplatform sonataflow-platform -n "$namespace" &>/dev/null && ! oc get sfp sonataflow-platform -n "$namespace" &>/dev/null; then
425
+ create_sonataflow_platform "$namespace" "$psql_secret_name" "$psql_svc_name"
426
+ else
427
+ log::info "SonataFlowPlatform already exists"
428
+ wait_for_deployment "$namespace" sonataflow-platform-data-index-service 20 || true
429
+ wait_for_deployment "$namespace" sonataflow-platform-jobs-service 20 || true
430
+ fi
431
+
432
+ if ! oc get crd sonataflows.sonataflow.org &>/dev/null; then
433
+ log::error "SonataFlow CRD not found."
434
+ return 1
435
+ fi
436
+ }
437
+
438
+ # ---------------------------------------------------------------------------
439
+ # Main
440
+ # ---------------------------------------------------------------------------
441
+ main() {
442
+ log::info "Starting orchestrator deployment for namespace: ${NAME_SPACE}"
443
+
444
+ if ! oc whoami &>/dev/null && ! kubectl cluster-info &>/dev/null; then
445
+ log::error "Not logged into OpenShift/Kubernetes cluster"
446
+ return 1
447
+ fi
448
+
449
+ log::info "Checking Serverless operators..."
450
+ if ! oc get subscription serverless-operator -n openshift-operators &>/dev/null; then
451
+ log::info "Installing OpenShift Serverless Operator..."
452
+ install_serverless_ocp_operator
453
+ else
454
+ log::info "OpenShift Serverless Operator already installed"
455
+ fi
456
+
457
+ if ! oc get subscription logic-operator-rhel8 -n openshift-operators &>/dev/null; then
458
+ log::info "Installing OpenShift Serverless Logic Operator..."
459
+ install_serverless_logic_ocp_operator
460
+ else
461
+ log::info "OpenShift Serverless Logic Operator already installed"
462
+ fi
463
+
464
+ log::info "Waiting for operators to be ready..."
465
+ waitfor_serverless_ocp_operator
466
+ waitfor_serverless_logic_ocp_operator
467
+ wait_for_sonataflow_crds
468
+
469
+ configure_namespace "${NAME_SPACE}"
470
+ log::info "Deploying orchestrator workflows..."
471
+ deploy_orchestrator_workflows_operator "${NAME_SPACE}"
472
+ print_orchestrator_connection_info "${NAME_SPACE}"
473
+
474
+ log::success "Orchestrator deployment completed successfully!"
475
+ }
476
+
477
+ main "$@"
@@ -1,3 +1,4 @@
1
1
  declare function envsubst(str: string, env?: NodeJS.ProcessEnv): string;
2
- export { envsubst };
2
+ declare function requireEnv(...varNames: [string, ...string[]]): void;
3
+ export { envsubst, requireEnv };
3
4
  //# sourceMappingURL=common.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAc,UAS/C;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAc,UAS/C;AAED,iBAAS,UAAU,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAO5D;AAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC"}
@@ -5,4 +5,12 @@ function envsubst(str, env = process.env) {
5
5
  });
6
6
  return out;
7
7
  }
8
- export { envsubst };
8
+ function requireEnv(...varNames) {
9
+ for (const varName of varNames) {
10
+ const value = process.env[varName];
11
+ if (!value) {
12
+ throw new Error(`Required variable ${varName} is not set`);
13
+ }
14
+ }
15
+ }
16
+ export { envsubst, requireEnv };
@@ -1,4 +1,4 @@
1
- export { envsubst } from "./common.js";
1
+ export { envsubst, requireEnv } from "./common.js";
2
2
  export { $, runQuietUnlessFailure } from "./bash.js";
3
3
  export { mergeYamlFiles, mergeYamlFilesIfExists, mergeYamlFilesToFile, } from "./merge-yamls.js";
4
4
  export { KubernetesClientHelper } from "./kubernetes-client.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -1,4 +1,4 @@
1
- export { envsubst } from "./common.js";
1
+ export { envsubst, requireEnv } from "./common.js";
2
2
  export { $, runQuietUnlessFailure } from "./bash.js";
3
3
  export { mergeYamlFiles, mergeYamlFilesIfExists, mergeYamlFilesToFile, } from "./merge-yamls.js";
4
4
  export { KubernetesClientHelper } from "./kubernetes-client.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/e2e-test-utils",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -48,6 +48,10 @@
48
48
  "./teardown": {
49
49
  "types": "./dist/playwright/teardown-namespaces.d.ts",
50
50
  "default": "./dist/playwright/teardown-namespaces.js"
51
+ },
52
+ "./orchestrator": {
53
+ "types": "./dist/deployment/orchestrator/index.d.ts",
54
+ "default": "./dist/deployment/orchestrator/index.js"
51
55
  }
52
56
  },
53
57
  "publishConfig": {
@@ -58,7 +62,8 @@
58
62
  "tsconfig.base.json"
59
63
  ],
60
64
  "scripts": {
61
- "build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/",
65
+ "build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/ && cp src/deployment/orchestrator/install-orchestrator.sh dist/deployment/orchestrator/",
66
+ "prepare": "husky",
62
67
  "check": "yarn typecheck && yarn lint:check && yarn prettier:check",
63
68
  "clean": "rm -rf dist",
64
69
  "lint:check": "eslint . --ignore-pattern dist --ignore-pattern README.md --ignore-pattern docs",
@@ -90,7 +95,10 @@
90
95
  "@types/lodash.clonedeepwith": "^4.5.9",
91
96
  "@types/lodash.mergewith": "^4.6.9",
92
97
  "@types/node": "^24.10.1",
93
- "@types/proper-lockfile": "^4.1.4"
98
+ "@types/proper-lockfile": "^4.1.4",
99
+ "husky": "^9.1.7",
100
+ "lint-staged": "^15.4.3",
101
+ "markdown-link-check": "^3.13.6"
94
102
  },
95
103
  "dependencies": {
96
104
  "@axe-core/playwright": "^4.11.0",