From d8ed50887064edd0fe2e590e50881eecc80e0d51 Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Wed, 8 Apr 2026 17:19:25 -0400 Subject: [PATCH 1/5] HYPERFLEET-610: Add OCI/OKE deployment targets Add make install-all-oci target that deploys HyperFleet on Oracle Kubernetes Engine with in-cluster RabbitMQ, quay.io v0.2.0 images, and adapter1. GCP-specific PodMonitoring CRDs are disabled via a sentinel values-oci.yaml overlay. Pin sentinel chart to v0.2.0 tag for OCI deployments to match the v0.2.0 binary config format. --- Makefile | 32 +++++++++++++++++++++++++- helm/sentinel-clusters/values-oci.yaml | 24 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 helm/sentinel-clusters/values-oci.yaml diff --git a/Makefile b/Makefile index 74cd2ff..922b0b6 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ ADAPTER_REPOSITORY ?= ci/hyperfleet-adapter API_IMAGE_TAG ?= latest SENTINEL_IMAGE_TAG ?= latest ADAPTER_IMAGE_TAG ?= latest +SENTINEL_EXTRA_ARGS ?= DRY_RUN ?= AUTO_APPROVE ?= # Derived flags from boolean variables (only true/1 are treated as truthy) @@ -183,7 +184,8 @@ install-sentinel-clusters: check-helm check-kubectl check-namespace ## Install S $(if $(REGISTRY),--set hyperfleet-sentinel.image.registry=$(REGISTRY)) \ $(if $(SENTINEL_REPOSITORY),--set hyperfleet-sentinel.image.repository=$(SENTINEL_REPOSITORY)) \ --set hyperfleet-sentinel.image.tag=$(SENTINEL_IMAGE_TAG) \ - $(if $(wildcard $(GENERATED_DIR)/sentinel-clusters.yaml),--values $(GENERATED_DIR)/sentinel-clusters.yaml) + $(if $(wildcard $(GENERATED_DIR)/sentinel-clusters.yaml),--values $(GENERATED_DIR)/sentinel-clusters.yaml) \ + $(SENTINEL_EXTRA_ARGS) .PHONY: install-sentinel-nodepools install-sentinel-nodepools: check-helm check-kubectl check-namespace ## Install Sentinel for nodepools @@ -268,6 +270,34 @@ install-all: install-terraform get-credentials tf-helm-values install-maestro cr install-all-rabbitmq: BROKER_TYPE = rabbitmq install-all-rabbitmq: install-rabbitmq tf-helm-values install-hyperfleet install-maestro create-maestro-consumer ## Full RabbitMQ install (rabbitmq + hyperfleet + maestro, no terraform) +# ────────────────────────────────────────────── +# OCI/OKE deployment targets +# ────────────────────────────────────────────── + +.PHONY: install-hyperfleet-oci +install-hyperfleet-oci: install-api install-sentinel-clusters install-adapter1 ## Install API + sentinel + adapter1 for OCI + +.PHONY: install-all-oci +install-all-oci: ## Full OCI/OKE install (rabbitmq + api + sentinel + adapter1) +install-all-oci: BROKER_TYPE = rabbitmq +install-all-oci: REGISTRY = quay.io +install-all-oci: API_REPOSITORY = openshift-hyperfleet/hyperfleet-api +install-all-oci: SENTINEL_REPOSITORY = openshift-hyperfleet/hyperfleet-sentinel +install-all-oci: ADAPTER_REPOSITORY = openshift-hyperfleet/hyperfleet-adapter +install-all-oci: API_IMAGE_TAG = v0.2.0 +install-all-oci: SENTINEL_IMAGE_TAG = v0.2.0 +install-all-oci: ADAPTER_IMAGE_TAG = v0.2.0 +install-all-oci: SENTINEL_CHART_REF = v0.2.0 +install-all-oci: SENTINEL_EXTRA_ARGS = --values $(HELM_DIR)/sentinel-clusters/values-oci.yaml +install-all-oci: install-rabbitmq tf-helm-values install-hyperfleet-oci + +.PHONY: uninstall-all-oci +uninstall-all-oci: ## Uninstall all OCI components + -helm uninstall $(NAMESPACE)-adapter1 --namespace $(NAMESPACE) --kubeconfig $(KUBECONFIG) + -helm uninstall $(NAMESPACE)-sentinel-clusters --namespace $(NAMESPACE) --kubeconfig $(KUBECONFIG) + -helm uninstall $(NAMESPACE)-api --namespace $(NAMESPACE) --kubeconfig $(KUBECONFIG) + $(MAKE) uninstall-rabbitmq + # ────────────────────────────────────────────── # CI validation targets # ────────────────────────────────────────────── diff --git a/helm/sentinel-clusters/values-oci.yaml b/helm/sentinel-clusters/values-oci.yaml new file mode 100644 index 0000000..0985167 --- /dev/null +++ b/helm/sentinel-clusters/values-oci.yaml @@ -0,0 +1,24 @@ +# OCI/OKE overrides for Sentinel (v0.2.0 config format) +# The v0.2.0 binary uses flat config (hyperfleetApi under config, not clients) + +hyperfleet-sentinel: + config: + resourceType: clusters + resourceSelector: [] + + hyperfleetApi: + baseUrl: http://hyperfleet-api:8000 + timeout: 5s + + broker: + type: rabbitmq + topic: hyperfleet-clusters + rabbitmq: + url: amqp://guest:guest@rabbitmq:5672/ + exchangeType: topic + + monitoring: + podMonitoring: + enabled: false + prometheusRule: + enabled: false From 0e32be7b24994232b344ccee84cc5b8f1a03d9cb Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Mon, 13 Apr 2026 14:34:43 -0400 Subject: [PATCH 2/5] Add adapter-hypershift for remote HostedCluster creation Add a new adapter helm package that creates HostedCluster resources on a remote HyperShift management cluster via a mounted kubeconfig secret. Uses NodePort for Ignition/OAuth services and parameterizes OCI region, compartment, release image, CPO image, and worker node IP via env vars. --- helm/adapter-hypershift/Chart.yaml | 11 + helm/adapter-hypershift/adapter-config.yaml | 26 +++ .../adapter-task-config.yaml | 208 ++++++++++++++++++ .../charts/hyperfleet-adapter-2.0.0.tgz | Bin 0 -> 21029 bytes helm/adapter-hypershift/values.yaml | 65 ++++++ 5 files changed, 310 insertions(+) create mode 100644 helm/adapter-hypershift/Chart.yaml create mode 100644 helm/adapter-hypershift/adapter-config.yaml create mode 100644 helm/adapter-hypershift/adapter-task-config.yaml create mode 100644 helm/adapter-hypershift/charts/hyperfleet-adapter-2.0.0.tgz create mode 100644 helm/adapter-hypershift/values.yaml diff --git a/helm/adapter-hypershift/Chart.yaml b/helm/adapter-hypershift/Chart.yaml new file mode 100644 index 0000000..8a7fcc4 --- /dev/null +++ b/helm/adapter-hypershift/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: adapter-hypershift +description: HyperShift adapter - creates HostedCluster resources on a remote management cluster +type: application +version: 0.1.0 +appVersion: "0.0.0-dev" + +dependencies: + - name: hyperfleet-adapter + version: "2.0.0" + repository: "git+https://github.com/openshift-hyperfleet/hyperfleet-adapter@charts?ref=main" diff --git a/helm/adapter-hypershift/adapter-config.yaml b/helm/adapter-hypershift/adapter-config.yaml new file mode 100644 index 0000000..338866a --- /dev/null +++ b/helm/adapter-hypershift/adapter-config.yaml @@ -0,0 +1,26 @@ +# HyperShift adapter deployment configuration +# Creates HostedCluster resources on a remote HyperShift management cluster +adapter: + name: adapter-hypershift + version: "0.2.0" + +debug_config: true +log: + level: debug + +clients: + hyperfleet_api: + base_url: http://hyperfleet-api:8000 + version: v1 + timeout: 10s + retry_attempts: 3 + retry_backoff: exponential + + broker: + subscription_id: "adapter-hypershift" + topic: "hyperfleet-clusters" + + kubernetes: + api_version: "v1" + # Use the mounted kubeconfig to target the remote HyperShift management cluster + kube_config_path: /etc/hypershift/kubeconfig diff --git a/helm/adapter-hypershift/adapter-task-config.yaml b/helm/adapter-hypershift/adapter-task-config.yaml new file mode 100644 index 0000000..98f07a1 --- /dev/null +++ b/helm/adapter-hypershift/adapter-task-config.yaml @@ -0,0 +1,208 @@ +# HyperShift adapter task configuration +# Creates a HostedCluster + required secrets on the remote management cluster +params: + + - name: "clusterId" + source: "event.id" + type: "string" + required: true + + - name: "generation" + source: "event.generation" + type: "int" + required: true + + - name: "namespace" + source: "env.CLUSTERS_NAMESPACE" + type: "string" + + - name: "ociRegion" + source: "env.OCI_REGION" + type: "string" + + - name: "ociCompartmentId" + source: "env.OCI_COMPARTMENT_ID" + type: "string" + + - name: "releaseImage" + source: "env.OPENSHIFT_RELEASE_IMAGE" + type: "string" + + - name: "baseDomain" + source: "env.BASE_DOMAIN" + type: "string" + + - name: "cpoImage" + source: "env.CPO_IMAGE" + type: "string" + + - name: "workerNodeIP" + source: "env.WORKER_NODE_IP" + type: "string" + +# Preconditions: check cluster details from API +preconditions: + - name: "clusterStatus" + api_call: + method: "GET" + url: "/clusters/{{ .clusterId }}" + timeout: 10s + retry_attempts: 3 + retry_backoff: "exponential" + capture: + - name: "clusterName" + field: "name" + - name: "generation" + field: "generation" + - name: "clusterNotReady" + expression: | + status.conditions.filter(c, c.type == "Ready").size() > 0 + ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" + : true + + - name: "validationCheck" + expression: | + clusterNotReady + +# Resources: Namespace + HostedCluster on the remote management cluster +resources: + + # Ensure the clusters namespace exists on the management cluster + - name: "clustersNamespace" + transport: + client: "kubernetes" + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ .namespace }}" + discovery: + by_name: "{{ .namespace }}" + + # Create the HostedCluster resource + - name: "hostedCluster" + transport: + client: "kubernetes" + manifest: + apiVersion: hypershift.openshift.io/v1beta1 + kind: HostedCluster + metadata: + name: "{{ .clusterName }}" + namespace: "{{ .namespace }}" + annotations: + hypershift.openshift.io/pod-security-admission-label-override: "privileged" + hypershift.openshift.io/control-plane-operator-image: "{{ .cpoImage }}" + hypershift.openshift.io/disable-monitoring-services: "true" + labels: + hyperfleet.io/cluster-id: "{{ .clusterId }}" + hyperfleet.io/cluster-name: "{{ .clusterName }}" + spec: + platform: + type: OCI + oci: + identityRef: + name: oci-credentials + region: "{{ .ociRegion }}" + compartmentId: "{{ .ociCompartmentId }}" + controllerAvailabilityPolicy: SingleReplica + pullSecret: + name: pull-secret + sshKey: + name: ssh-key + networking: + clusterNetwork: + - cidr: 10.132.0.0/14 + serviceNetwork: + - cidr: 172.31.0.0/16 + networkType: OVNKubernetes + services: + - service: Ignition + servicePublishingStrategy: + type: NodePort + nodePort: + address: "{{ .workerNodeIP }}" + - service: OAuthServer + servicePublishingStrategy: + type: NodePort + nodePort: + address: "{{ .workerNodeIP }}" + - service: APIServer + servicePublishingStrategy: + type: LoadBalancer + - service: Konnectivity + servicePublishingStrategy: + type: LoadBalancer + release: + image: "{{ .releaseImage }}" + dns: + baseDomain: "{{ .baseDomain }}" + discovery: + namespace: "{{ .namespace }}" + by_selectors: + label_selector: + hyperfleet.io/cluster-id: "{{ .clusterId }}" + +# Post-processing: report HostedCluster status back to API +post: + payloads: + - name: "statusPayload" + build: + adapter: "{{ .adapter.name }}" + conditions: + - type: "Applied" + status: + expression: | + has(resources.hostedCluster.metadata.creationTimestamp) ? "True" : "False" + reason: + expression: | + has(resources.hostedCluster.metadata.creationTimestamp) ? "HostedClusterCreated" : "HostedClusterPending" + message: + expression: | + has(resources.hostedCluster.metadata.creationTimestamp) + ? "HostedCluster has been created on the management cluster" + : "HostedCluster is pending creation" + - type: "Available" + status: + expression: | + has(resources.hostedCluster.status) && has(resources.hostedCluster.status.conditions) + ? (resources.hostedCluster.status.conditions.filter(c, c.type == "Available").size() > 0 + ? resources.hostedCluster.status.conditions.filter(c, c.type == "Available")[0].status + : "False") + : "False" + reason: + expression: | + has(resources.hostedCluster.status) && has(resources.hostedCluster.status.conditions) + ? (resources.hostedCluster.status.conditions.filter(c, c.type == "Available").size() > 0 + ? resources.hostedCluster.status.conditions.filter(c, c.type == "Available")[0].reason + : "WaitingForControlPlane") + : "WaitingForControlPlane" + message: + expression: | + has(resources.hostedCluster.status) && has(resources.hostedCluster.status.conditions) + ? (resources.hostedCluster.status.conditions.filter(c, c.type == "Available").size() > 0 + ? resources.hostedCluster.status.conditions.filter(c, c.type == "Available")[0].message + : "Waiting for hosted control plane to become available") + : "Waiting for hosted control plane to become available" + - type: "Health" + status: + expression: | + adapter.?executionStatus.orValue("") == "success" ? "True" : (adapter.?executionStatus.orValue("") == "failed" ? "False" : "Unknown") + reason: + expression: | + adapter.?errorReason.orValue("") != "" ? adapter.?errorReason.orValue("") : "Healthy" + message: + expression: | + adapter.?errorMessage.orValue("") != "" ? adapter.?errorMessage.orValue("") : "Adapter executed successfully" + observed_generation: + expression: "generation" + observed_time: "{{ now | date \"2006-01-02T15:04:05Z07:00\" }}" + + post_actions: + - name: "reportClusterStatus" + api_call: + method: "POST" + url: "/clusters/{{ .clusterId }}/statuses" + headers: + - name: "Content-Type" + value: "application/json" + body: "{{ .statusPayload }}" diff --git a/helm/adapter-hypershift/charts/hyperfleet-adapter-2.0.0.tgz b/helm/adapter-hypershift/charts/hyperfleet-adapter-2.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..25580d4c80c183d8f349951a68a4ff3cb4e81e92 GIT binary patch literal 21029 zcmV)GK)$~piwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PMZ%cHB0yD7=63DQa5Dq~%e!T9PlB-s8!UWjWSFvUMam`LeUK zBs7WcM#LrA$Y5-0$LLZMKo%XpU3Y#39T zcSuCioMxMQW0K|F8JWcYaW_w|*Xuof{22f3^?KERdr!A^{$uOO*3)O(JH5wS&;Fyg z_4wJ-$NvGnJ8FO7Q;3{o|Ixd3Th+n+i##M{KhjJvp7de59gsA&KYQI?x7Ug2G>E7O zGnVG~`n9B(SCV32S5XXfV1G)Jyc1klAN%FW>}q02#fm;luk~`OMx~ULd1qcnn?s;W09J<4IoK` z?HXW15(~O6!Fi+WL#q`yjcoTTn8uN|6%=8(W=Fw5egKzr1}rK2hK(RH`uLZrq<KBqJKZ70btvk12#wT*2B{Lbcq`_dd|~WRk{I zY?gd~v!S5sio4An9{^3JFeRCQLeNOgYfi)^q$DE~5rDH|c2g$1AVIt!g2KC#!|MJg z;k94?e7t}1>TrMm?0on5;QYnz>Hhh?B*(zp*S8S-)!o)K|rR^p%#ETKZkDlGih zU4P}6zMM7a;Mre?lG3iX2o+iO;g?_4?~DpwWFb|r8dqAi^=qxRY7ZALwQA{r42LXX z`K%9rd<@p$?UZI2i>SdZkZdFZ`9IlO2gOQ?e|0n%{IN;%(6rIGtW^!C^?J2>*NU8r zki;xON}&lE#5B^g8vsnm`xA9nxGbYOnYj>=rriAT}l03}Z49Qo7Q61JX2fB>T^Z$g?8Mi;Tjg5ILk7 z?H~oMYfz^&mqU|Ng4>%sRK+|+AuXp!Y5yq^prNuvjwUKl3RK)gg0$y98kI~6TTpY3 z12`BSF(Kp%w9C$XcM;!E%$aQ2M>Cc3i}_n~%p!|5p4I?CO*czfdynhGD5N~gwNOq;oR4vznb16A zp#YjhDQEI58=4Bovb=)ny_5XoTO~B;6KbBPhRSjt^0*IYd&f-`|JwVjTTwUOPf|Z(bdIe}24s_BxPaA)Z0{u$hv4 zyqWXOun;+)Y`Pq|1akEA`RV@N$^Kbc^;O0vef$aX$dS?f2Rb{UL-Wp&N7ZNh{!&hu zde^z6vmjW5A9-9%C`@>f=qVsk#3VhDSTA4^(D!*pRLLVOSQ)7rf!T0mYY6X{9SBlF<orjo|;5OJX4n8Oueirb2q(iFDk zmxO-vt0bR8Hl*P!j46yczZ5}ierr)H&FE>)HF41QG&E#lnUh%`9oQVOWK)ctH=WS= zp<3VSsb;4Xj|pkUrC{PoEA5nery<4HI+mA#$Vn2BELsoN;GLiXe)v|Hi4zK^jKnW# zOlGGv6=NPp&v$}YclB8I)D!Wv zv?2NA_cYf#vRbyP%l}lbO6Ix-M=W8JVggAq8PH5hKti>S%jRm#!Z8p?=v6#3;u@qp z0x{-A97!QgvVd8DLL`aI`1;TbYKt@B5j~|b4RfC1bf!GoO%k3XnuVU>V;;dvCb9xI zLU>U`BjkF=FJB<{reQ9CObLt0fW<7IK~!W?w3F;6$w@YY$W|B7maPIROKC!qkZu@l zE)?k~BM}wr-9U*46PD~Mz%hllCsI~^mk|4=o$wq6JRh%jSBAcl$Dg%;N->bo?JQ;DLTms zmU3%CX+$GnLyys;!b5%%a+Hz)G7{!$p7suptX5xhYgs#EqY*`FBJdj@Kt3k9k|&Ea zA~^-7mU~_xOv(K-WUdOEBFF|LY&-#+7-3P?8)ZZu$BQ@y5{8oRv_~}GfS8GV1FlFO zjyE)j4c+pFT*~rwL}Qw(b2^Ze;Vht*Q;NzFmFt5>sE>(&Css(sQv+5i&(>PCrFg$c zi4tYiE6ue@7^0A1-`gFyd{t^Y%XcY{${!hzssC%hl87av^5t)Q;Q!cESjKmbOrhie z$O?JbZ<92oNu>eHlr6?@<-zyeVF%vnR*vS$!4EZEI&iIhU zR8>|NY$sA;noxbK5$J`ts}`PGqs}ggq#hx~OJ1i-gI=d{Je3_4&%P-Q*^p5IS7VCX z5~kVG05iso5CH>6Y{0WksPqAlcU z7y%pU9&$QKW0F&QeTlokDT`=GGFPk>>eXDyR)2Rz+GNBMDbQ~H72l@nB)X=jwRRen zg5qgTvb;!}`9~q>u!zk@kcG9fN_VP}b;wys%<>L|YNEdE4Lx2)JdkA2wb1v!stW+?aTtJZm9_k25}zv|I>yo67P$lY(E!r!<@4 z$sW)0xGzjd&O#7jMp8K)+>;Z&5wKa+Pjmx&5>rRXrq=dOUJ7VWRG*}b7xIj9fffMM ztxdnr>vC-jD6mB2B#vn$n|Sl~Y`<^rGlfx4b>VGdgcG5}m3hr?XdxDtjpPVS1Zzs= zZ7*4_m=rS%7D#L*c_0DI21#9@H6)2bh!D5=^@eITzOH&$kjh0R6&~gqu z&4Cwr*RVC+QnTcHDkq34No`8v0PLTVE`(b~Z=uje;cP%5mb-dBCW);hH-DURdZr4? z9iS#qXqi}2RE5ZL4ihPIxyU!7GD6u`R|b8^XiSNqeaRJcNx2`EA~asl@x+SQO6e@I zsnqDm)Tf+9s9(Lx@O&&+x~yYFaf_xSBv#p~{9+V42qR778H)7kG6M#5$TNx25f!-0 zgVu(fsLH5()R!)Aj1X8J2qad*j0@4R5+aoJ?jb=7Z-_C3WG_^~wQcFBZBcV`gy!#wpE2 zH&1V43be%j=ke3W+ZFqtXFE@yJ=p);$MeS@n~#G1_ogXy{Qpp0K`=c@|bM;v8L9*c_gr__BC!3rW!y$VQtxgLx zESf~}&j35RXLefCf)s3L@OwdGDUe5!W#bUK!B14d#`3vrK@Laq@Q@TjGL;E+k8w(K z!e|^RB?z9sXk_F7Xb)zHl$UQ#Wm(CzL`uW;Zg4R4c$N`Qg%%u?T%IezVLcR)eXd26{vh|l1w9Z@CUs!xH;fa=~R_d%Os(L%7@uVxpn>c_z{P72{B#et_;V`?Z zBMXZf(dke(hIL4qc1yF8F5{cloav80EG^nA9r#eC*VLbH-$#eurY zm^brynP04{k5`qwrM(CZC9CYN&jXvd<93*dX`rS5XsrZbF~g|h(CE}lTbyfP(X%1d zhA)kH^a^%8Lz(LKutL!}3|EzHU1L6Ohcdi|xDDP)OSctpR~B9gf6I!`!J!<}r^lc+ zJ(`ER%}2pYs?LQzL6HNtwAvmkx2hjW%p#IgWdL>{_O#xSMvbK4k5d*v z-GQA?HiY(=h#%;zQUzLGuUqR_89>9AqC>R}Z%Q3*6AQ|e758rXDO&KM-obzI8!+uc{w*^EY`XhH+B3dt<&t*L0~r<^md zKYBp~%Lqa>vYa6_cBaAw7L}Q^^i`F+TKp@MzP9>ZO8b?4p+_9TFY$n0gQYCm(p}E7sNA&~%pzd-#Ah1ka}{>8sPEV0I@e!`QgT>zHM&y9%~qrX z;fAZyf$*tUW_`FzR%c~&4J)+T`#W2ujvK|bSL$5hv0=5&Eql`yI~U;nuG;!sUt{IY z9mBO(@8+Z6grdU&(Rb5$Z)mSX&>mae8?PpIW(W;0q06?vQlR_Kp{xF~A9O>5v z_jj!j&vLJj{9WoiTC$%WH&M-Yb^qmvohxv40shAyl~P^#-ht}%5T{&SlQ&fKyW{oR zT@aOOA{jQ-WL8jsG^=~iU`-{9HM~~Zp(WrvD=b+c z_3aK;0O6Z$;k0;fRsvGB^pa(&?^XgN3bY*b09D`31>`Xfxu#HIVXO(!bHKf7Fn9jl z;E1Hj@O_vn;m}lCRuzUHf=rX=|(=$RT z+7zPSgpLYOTJB1bFryQ~@V*0Y9pu`enOKc{rn(8Wb$djl97N=d)n`McwN>`Q@ z24srP8&dgQ56Z$`VOX~8vVoa`&sX%cnKN{zEEU@e$DEVVdUJ($_jC=@Dl~K8Q;cfYXDhCm?Lo%W99uaiC3&-l#Tss5Ic#&y- z)AepCJJ%bG>lVA(h$=N!9+vNbdmm$qD2dKvbnZP@Zl}vfSF5w~_$r>n_c1_q>QntZ zXT$S}^66b+m~(2bJlH0W<9i&jPlGoY_#6P&9Dk!n^{voQK}iJ{#t(Zb!?Fci+Qs_( zlFpj3V~Td2l8ew#EiYUDBokkb!FZ6L=d+Y98HHDNrE$3a>7Qcme3kUvQCBqAP|bVh)l&t^Mah`(G#e&27Q~E{XrS-Ro`n z_P^V`?d_ch```O`sw~f$ac%FRY{|+fp-z#_>2`3*lBf@_dB*<9lbpoIJlfSIXckOp zP9l<%zI8ZTV!-BHbt`43_WQ7k)kT$KYbIFHv5HiwX|Y#l=p=$sK_pObY-uVsb&NDM zu+rhn34wCmF?cbd92-R!+fEr>$qg+vuToh?S5lr=4!WA>c5zTD1q+~K9MiOJN&9FT z#gp;bv{6OU!oE=rlqHOho70~2jZA4qM%4K@cgU;t+~J=qVPZk#ts5dkdEVX^K33@e z9G&&&WJq~iaxy}kYTVgJ9EXH^T6iV!f}(i^`u zoV)&3Ki?krF1LQQ#aB(?Lq17)LX*4?4Phw*GmfR$LHm-AAGI57QzT6cM^z1v`N7?l zBq9$4wAB4wYlYU_t)RJ30}}r5j1bKQ^$p>etZ0*b_j{Uaa4U7Hw$pv?mG)&m*W>>;I|VM~|6A4b-`4iCrw{!9KAwdt zuTReVzXZO&63d^@?tNC@7%kcDJBq^w{)fr7 zB=W$ZJxm*|pPaW||JK(_Hi5S*ZR2g@YW9tR2~W^H?rQ<<5zpc4-`88v{*AK)S{u-+ zKz19g`r+L52HEJAbg%oP>OmPQzqHhKP_F@%4ND-prc!W89$&BEU~bOFt7nTQ({jVL zVTSb85N!-M-LOaD9hbXFRiV3kll)s%aJ<9kZ$k5W?#c>{Vf!(de%mR%9soE$##;ZSPk*BR(Y!Kw(SU0P} z)-M5}Oj#PL*X&)Ef~wlNGyt7rU9uE(qg6>A?i}Nd6~Q+fZP->c@Ig7TDn&>f)0l~g z3QXr-S5|4qW9=Az6<-|vZ`gAwdUcX=BJraR-3i989*oCdAG@`1D6+U|mUQI)1r@PG zQ`Iuc|AN}4P!j;h*kmCL$H-(sH_k2hT-FzyBFEoDn}w~!yB^dd|I?p^@_%Wmf2(YO zi{<~VC)>SV_5Q#2>>>Wwy*$h0$Evx0DV?uSscO62tsD*1?t%8%%T5T(;x?3^J^jPA zVK+L)*MOJm>zg)E_xJ7A3@x-VLd_823p1*4Z2p&+kzj(4xio)dm@yJ`9=HaXEneImS z1rZc|BU^LsD_ZL~3^69#Po9cm;x<*W6Pe%H#v9REhn<_gTlBiTWxqk!y_Mpp1gREn zSSQ~uY$5h&>2}mqYpL3CRq2*}Hax1ak!psY5>74m&o%l`!&*J+*;0nx6ez9hH{p!&Mektc{}5=EAt+WvwOQ$(_Ee2(SUWwHyTXnJTHOl6z(T6#A6nC3$k_ z#83Wo8!8DKz;w#(-Hra}ko$u1&%uG1i-4_dAF>YX->L;ww4Rb5KW;XBwEl)4KlU2~ zGJgEnsy1`%qztRoDf5Z>9jr~0@v!$wd9L|OCa8*n1|HkcEXhW~FJh9Fexe5bY=3^? z)Fc4Bo3?2(tpoEqV|A!EXmU=YCFLK!{r>!L|Hu8q3VfX?aPIYJ%tu|!W;_q@ob=1r zY^K@cqCu^*plO|@`Bp7Y>)}kBT7Gfz_J=a-;;Vzh{pMCNFQGc3Z8{pg+Qi-FhNlW^ zHI?;3Z0g~izI$=HcXDuicJTJi`N7Mjh+V<6bM+b3eK>o2e6V+;mglOqVg72FgTIQ} zb}l`vLiZ~wU>l(S?|1v}=Fr(q^t5x)P!UkP#D4xE)I7D^e-&U0$k~hM(~ek}qZ76K z)z|?WO;*bMEohkxGp+Ii&+?Xz*0X+-9l|?ky2-AZ2VYN!b*H2E!Pkbb5^H^$(YqOc zn#W$v33R6;cY%N6Y4;L_`DH6opDnqh32$e&*+Q$!#v~EFGbv;0CeUqUCKYQf@SbW) z-7*ub`&6>)Hnmc}^Pb&S#lYbvp5A>E#;YtY@ayk#*jS?_Js1ogw`aL5vtJ4a*l=5E_@(#SdL9VfTk z&0721_h0|hpN03oX*9Ui#jk#r`F}s&dQ!>%^z`ww-oyRxeLPiFay_lKD?4D{TA-2)-n|J~*52I9 z-KD*5$a=qOtEShLjIqo5U0YO(ucez{%+H3eKrG6ZzodK1K;QF)_Wy7nl7;&}MiITe z0IbPeFmc?_k+?a zz#DC;8p9SlHXO3(!!NC6XLuV7S$4SfD|WP0%P9cPrzy<__zcvQ4_6JpHx4SuqvF!@$yWK>;P?30)!}{2{45wz zU_$-4lzX%Oi$Vn{>mwa0sHdJON3gQGWT6R&lhwGI%1@-!Mx=dwU7gx0>tAR3yfGVCw*H@NKdajR zKYQ^1y`QIgciNbGwKLDbwX(Tgn_n#0X>hryji4dR1dNucFUmboD!sNt#)n8Lkzrb>ehYry_7)r|KV` zclIihdqq09Gk5=aywLPN_AKrf^F6PRHoWRRxSzNxd_ObcyxB1KuIrTL|J^8on-cLNcj5&$mK(Ypw&!8=_pnDAj^9QaY_=7~`Bu3_w zkEy}##l^*dh;cBMfSD=+^)(6I%}`blo6s4SKj?chNn;Bf2-eot;0HQ`W0H{x&1oir;6wQfKEO+AE$8uAre1JE_WI!|EL+d>ZAK+r^0?PPSv*Al-H~R#yBszfUHB) z2e_zX$^(#-QCTx5BUuH{Qhqz7M^jl^zSFn&ves^VMP@?R(mqVGu(im9GN0(hFRe|I zrpE5I^{cK%vXSuXkZe?7=0#OYZj)3M$C>rd^YG_7fd|3CP$iJml&LcX3Khvl(pni& z$|UUomS7#t5-^Q$LTQA_F13zex7DLm5mHY2r11lEDlfZp5_|$9*8>Cy9#1H zz(t-FRHM04S;}&rsFf#`O->-8pzSb=}V5(=YC(n#du8R5y9(?bMO zB0zycvN0ROU*L?0%PPy*Ew>2dNJ&T`;a4E(N_`$7rtZEqYD>EXK9k^N(d?A=8T0~cL58&g4b4GCGEqRe)R9h9Bh zK#QaqFL2<6pfKXL1Gr?d6H^M&EFlvXnk1nrQ{#mpQtdjL%60lGw}QwO;+_-J?Tv{-y4+s#R7r(Og^2 zXG+3LGNO@UoQl9cz{MXQ8{zpGwrrLuwnwp^_mA%>45fN#1S4JfN^N7wyB*ZRg=@LX3*C8)4`Wq@}N9IWmT38d8KkK!b6>G zoY3w|NM~LudZ$N?z~+%qB`$k2m}R6?4Z6Mn$_gYE_>?G$RLRyC5ginxb2aP>$u1=7 zCFdJT31>nx3DFOQNQP>PNEEd7HCi7oh9nmBV!aCoIkF-W3vRN)BI|aDeg-9qfp9Vz}t4tx88?oWDgC0@EFPGO-Rf8M8q~GTeMo{Xs%4rI5ws zsJlTwp9}h~_a`sFt|MR$T)M2YmFDUE;FEJB16iEGSHb}J9zUuhx$(M!gO^L;RPyGc z(CfUCAo!GK8H=dL$(tRu*O#`3zR|K36{Zu%x~Kv4!fpg>n5f2|;hZS#RT*gT)0ife zn#v9vDO63Ms1&%QGr91~2*8?@#WG4hrILqUi$o<~v+3VI!Esze?#67wa?uUb0txr{ z9pD>@sVBYO#GEXu@PB-?avoL%|MhCEW+vW<0|@N5{1=?*4n>S8n%8t;3wkS>y!Y>QRBV|bCfs%35Q|$CC`d_0FIG~%ffcCz= zWgN^5ruD{PkB-9GW<#Y#%ZcET9=z;Kihk)N~%CyEOjn!35CGxz}Hfyg(^w1`LgGf=0zF=XB@R;&V7|S z7S<|k0D+4%V@b$T664vJxYtZoEvr5`mL>yo6I2-~@PpR9N09N@>fh8T)=R#$23zI; zXC!^Cng(OIHwsboXJlgf(9wMmMnB)c(A47AjNYa~AW_s|WYST$ADw}6Cx8;7ZfYVAtl9IHMPYw)laYy@K9VwJI%ME(Za;gn zz4O;+Po90d^K3Kbp^|zwM=>9ec=Jk6kC~lg&SSAT4kym-P*+UDE{m{*O>N%4nTSm? zAsI_H1=)-VcDD0Rp3s)=r<{)GEu551pJ)d(Ykg7HferWnb~m~!Lx3gre@~ulJ+1nG zZ9U}wyq~AMyeS18)Imio!=uE^M5Yv=3B%XjsXHRIFPod@2Act;SBlsWHB~v}8A(LS zvmDfwj_6jTtq<_h_TU4Y+2R$!bgnpB5lw>+@XJMcGr4*3YrDJY{9cC-E+^(mJhRFf z$BaOuC7v1HEV)W-Z5`Ao<$DK~zOzzYm=G%RjH~v|k98D9@o&?z3is`{jGIM9Cp@Lc=l#$ zF=FpuTc(beDN+Dd@b>l(<#9QqdL!J+rD#bvJ8s)##$RtK5$9v@h)QWQtn&tMyS&@dyLq#sd(K=D-%j&$^Tt-C^=c_CbGlVMps9Ytrafp9GMbJ7{b6T_4 z=t#kHR+qcDmMo9Kthz#=eUTs%&P@$Qg<7XvN0 z`|F|hcF|&hJwBQ6q;_i-$3`X)AjTx4%2q^T$6RbBky2kk8k3NYc^uJ9-xQe~e9F-| zf&m@!%st|JS9W-wK?Aokp;SE$nVW)a@AdAR@AuD-RPfQtsj>Q5CkpdlHe?bj3utY{ znlj%iyQjitV^RmDm+_&N=4AaQvpay)35BhW=$Jx$)CXlqRvP#T({5e;^fyVOf43SY z=nSE%Yx+cO(r}D)4H~Z(`musZ{7SSn@l=}PXil@IhYNut@s(L4^>%LqD zK&hUv8vePge2o-T-6;ND*T-r3+|vB@pGD`traki}Z7i|>-r3rET=oCj={=nP?&T@Z zf97;onq-!O3`tzQ2$umDbiM}(+$M8Ms#94HZSpR`%x{ zL}Y6x<*~#{RE`mm6f6G02OD$FXwLae9oU>iSF&aAcq5y8Me}fs$7)3l+Lqr^MCI_K zr3w_hC`KufQ0sCXf@Vo1N)wxs{*@?RKMXy3R29f1m=^_$BVnlqQZhp|aHGWMx+N?S z=44xz9yRr;skVZ$Y*7}`$FF6it7I_cASK{8K7f!*3R4DINu=d6^T|ws8g=DdCwp>g z48R;E4;49xkR~K!{F;ZqM~_Zk?C$x(eQm(zD;|`}D|4*raW52(6r;k$g-Pdw-~;|K zPp4E@A^E)SgRJ9=IULnH zW~Q_A8z0#2RhYGZydy9nEGZM~U_(&k; z%&~+A#kqU89*$!A@7-JiONR6tA1oP8Mu&Z9xuN(s&8lsc=VrT|sXerFVu<%tt6K5K zUzTcL*hRl?{Ku{5h|QW6Y<1z$qa$z4qkK{%N)$2|e#i)}GNv{AMVxC@9qKS59jQLp z?%Msm#xEq?BeQo(*gmV-K=U07eZ$*NHHY}dx*i}0q2FknY4tbDrJzR%UWx>0C&c0+qfR~jre06!__B;^?mi|7I_I6nl%@hR=%bYz zjc3r#QRkj(r}QyNqL?Kky+ei>5o5GjvVdhNNkUpqL>f{KehFs3?ffQ`m4)u*gv3Lh zO=txFIDPv@71c@JJBkdP!dS$#VlVlPpv!0+Nxo;3$4dU_NhZz`*7>{4;;qcFib<%F z@o4-na{NT$#IZC*vhhSBEB}bPB$H4Y(a3XA(=NM8*>@m`*x}1epmA?JvrwiK1WS}1 zgmpJ8Hx4CC|HcQxVPOLjDrKCPHlkB*Aws|Kw|Mi`Cg(ISoaXc!yytiRHiu083j0(3 zxv6!zCMH89N{-mY*UQ**Z;y*stfSId>7vZ z^#8A-js17j!G8Bybg*(VUh^wB$k6~xpj>d~c`S#NP4T!~YLCp3!O<~EW}-cHCHF)Qzp`YStyd%# zwAG*#dE9liB8s`{25L4CECHTHieEsdc5!cYcO@=W!ODrjShsnqGc0&wly>g%W6n`W z)v&VU>PJ4^`8jI3hOPm;LZu|5@?=r$GU=dK*jZ z|F(KN+m-vD?I%wk;=kX^V@>XwjP`1JMI(!8awuc)m88+T0yV| zhu820c1+XJJ!Cy_U5j#!!AOI+3?FI?DWrfq0D?_`l18}gd zz>t)km&l%Osk&>jm`2vQ3iNiod-nSL?Ctpv?_TUH>wbVW+*y^?o|YZ)^5t_==EHLz z&o%jf^^RbrHWu^$?cUDQ>iz%DlgAJFzwYH($p7=&?N6}gME=rerZ_HjRQ5J2P?tUZ zpg|da5Ge+w&J%rTQF#LEGW}YGd2fk4W67wcIjVxMdA0 ze-pqBv8n4VobI3ec(AvBzPq>g_T8Ja8?~dfvGfFw3{7qLI=hwISZgv3mOEHS?tgeL z!f3uTbz}(Ez`GWS+>2Ia?HTgPgeDP-SA9dqadOW4%^d)f%PFpt=16F5zS@Y(zu}yADld` z>ipPsZX<>)&S};THz4dvdWPrEp{1a$cLn<=ZLh=Mq37j#{qCA@e(C+%)hE|KJ%`p= zRycT(kC%G6IywY$c6PXYbZt4gA}5oyo$#yn`Z{!=eC4%xH;A|yDq4`yDqdi!rwN|dr{E&W0h zypk**6}>t_Qq@4h z7-mX{HQ!-VmJIlmsv;VY797eDoYnz3=e1T`_bDI(u4o*~e~E8usV~7Qb_2c!__k}I z+03BHR_BwDgtcigvY78Vy__iK&{73w=MtTt2vyd~=u28NRlKV4H(^B+*(XGfYY+9M zUDb6NK6Q%-5c zMDJdBXW8b>+2&fai=C3ys-CZv!>;vuoD^}+(wMrDX)%GF%x+dI!Zr4%34-Q2M%V=y!*SR9ooHadBQ8Bp*eABNSE9M=X^h^`cE~B}*I9!MC^sx>4 z=Kk8No>3xra%aue%F2UQ>mPj3I9B!avA{8Pb|drXh-II+dmho5B@RUO*qhBc-S+@6 z0jU~`763_o=xg94QpZD|YiaDn5K%c;mjh{@m*$>zS3I^m&>byCA6oB{%Y2-R=}@Ii8%@sXS{3nx^u3F`?)3^&5%FI ztV`iCZJu|BQ~SJGnfUY_-%f3?WEgZiE0UZ|y#2)TXI1<5%-P5w-`VDs9Ak0ZYONzm z^#;|1yg&aL{$$};6rNtPG^LRRq|F2BB-WttRkLwdlEg}5{L`W_eh006Q&-VezXgA3 z0KA`NJnMd!j)|Zp_+sv6(1#l%4tsS{sWeA)P}DNJ1LkulxHKfM)+3M{5Ml63^U}Nv zL@PM0UqR$Fy+K2=+I8CL!?l~x^I=;l3(D_Dm9MRYn;;;q^-oAxAXKeBcoZ++p5R)n zVMVXeM&$I%$uIex_K=w?1+S@GW{%}89K5uubM0(la}^bWIy8*dom0*tl@1`~vEj<1 zprz!?2bg9lI(O98PWRn4(RdkW>q~~Mj&}*af)9Y^@c-S~?)9E_dRv{|_Sx2xe(!O= z_vF8N&-%UIf47W2QJyl-N$4E=>&5eO}NM!@Mz|%2fVX0BII-1TzpB-P3(W> zIdjfyW10Qu_D&`KM>9!NQKn>eLNwIVF}HFm@(zZ- z=rrkUoeaphOcN)EZL5;*_}c2=>u4EkSB=-ZWHd>SO;W->Y^{<4nWSkSeJAzrYYx%O zj)q^k;;r7_RvWtkM$Irm=?<;8ZIx?#t$^}Q>Z>RyeeEh$enqbWUpVZP(2p@u@wdG% zDp2mf>2oXo?_D9zZ(|w%-+EHD|9|py`yu|*y*w_H$DEL)$j3Zm|3pUDz5G^4K4Cfk z36E(5&#(JrRk?i0SUx?QHy7r&Wbl~#q>tBj$7P1ioWqFbjzhnr_r|+AlCtkJUZkQA zzd&mscYqfBitU#sLh{pZd?3u1Da{7z?TF^;pij|KG=R1OBfaa&Nf} z+{-S|aV&&xyBqMBp3tGJYFOd|j0MIh6F1Cx_|XX^w8f~Z;G4){M;pyHEf$vSv`gmy z*6Y8)+OFEhvi1LXr?&o|K70DG{_o?d*b_I>V8a=Im4p3#ihK>Wm$zK+pL=5N>ACIt zU&RY>@%rC-yz{KO{vSVm_Hh2YmuGEm48x2TKL zt{8*zZLS5G#RF4H!wq$NVyqz(N~2&6O^yl{t0--0AuuY74d|gF|EQER)>pX;?*!GM zFp`L55$wh(OLUx|E_V1hqiE+Y7`jcb0iem-tP(6#xw40Oj7Cp0=nT~+kHLi2@IRMj z;-Rb*kN&|24ct;MD7jpgi9|5xS%bfIRCKHn&4o$8AE`JqdU5K|pUUy_)3RH%sJ$(V zy~(JIHd1@W9kL0_`{)@(!kkU%OG=`cB~&{r^QAS7q$Jcd*}nd??oztIt~sNa#nS|zdPAqm8d#CTj~GnGTr#TAGNS4YsH4!>UeZaL z&t9^u4}W|N=IV4ijb!VobqOx7{!Vx6ul}cFCtTLS@D0z8Gb(73JLad836V?Xm)7Qh zC7WX0+JIIkZ2ejyIU7~(1$|QiL38NPAn@0RNl@y$E4yWs`|RoY@$T8{=dG{)aDVqZ zn;Kbmt5d(bzX_gv^lu?64!IImamLN(&^<-y7Gui6pI(AKzB zZNk5*pIk;PgHGy}dwFnD?MX-kf~t)WSkjQwl z{cZp4E3fn)mAWmheCwCCDI3)y+H%1++#^EJs9si^YskWqO8h{aYJc?&8lY|5$fNZ- zIAhDvuXP-(|2f0HN)i}S3IWuDA0kgD-Cm$B}!a#&xrQFq!Y;4feQ-1&Oa z`8qoL`gQ;7qyE>Y|FvG7n~%Z#U)8yRa?SV=MXxfJ=M?pvwPjEP@Sf#0hg*RE{Li5B zPDdYIMty#e(oaWyx}8PTcch|@mWtm7T6O5}wBnu;lkifM0Ur)_kGvqCpOy}F#ME=A z#a17FX)p$Ag?y40IqhN$#zdu~7kRBuSeB7V*Wa5(cSQ609R1qRCtdN}96kHciX?yU z2=c#lB*iW!DXnNqLOyBe&Hb;ESAP85Yp;^66;{cZ7#Hk%`_9fLi$Pn&l%^_OUm2ly zy>ZQ8QTVHRR_F%?@?D`LSPt59e8!KcA>eOwpH@bS8!31h|IlSsWZ(r%MAiC33Dr!X z1CpvKTY-t9!|W?V}U@ z5c}D>?mAl)osZ^WQ2TULB~N1yw@eZ&0io!jGgjD;&l_fY+Q1dKp~!#3BhZb?T+eGj zi{aXLNH_5BCvEA#bWma`mVp5b93&_-8tv^J$pU2=<+v3vkf{} z06i0iiWkT97 zuFW`$zlEitO_6okw>vQxp2hpLT(_m1j>-oo6&G8dgOQbA3og2rcr7h`cI;V^u4}Iz z=Kpi<0chilCl>|AM6_!>jmVgcC4(X(5&Z`mw z^uLwjm*@6}DNn)kitc3?C?{fSIt&#O+5mpvdU`TAgKvjjrKD-@gXwT*^Nhl_y~y8J z`6+oz=N^J60xtZ}7yh2mSl@DNM*s^a=J`IY>PsswlAkUM%BhkS}Ys$4sy8Em1pcJ`@Pw|%7aOt4D?btkKG5MhKyzKY#sJ=Yr z@A6r`2KlhHkl~^$uyR?iczDw)PO6X4a=h!Z-+sMslNWP|yV+1V4#A46gz#R%RTANX zlW?kXz5oAg{KO&quZF9kVBtnW;AL^}e@O1cT=)+HDsdy>5bS0YqifJ85XL}HcLVa|&JW7TwYdA_ zPX>NaIjt~Gu`g`R1@MGiY-mr9J`6S0Kb1+WUszDF^lNhKWp^aKe|MTHEa>L{YaxX8 zKRuzKJi+B;;mak&5Fu>G7JR4XkDblm*6F@>*Thul^p~I~niPHyQ6!w;T4j%>%-!HT zg6aP~4Wi|Gcw1^!p}cW2fR^c2C1K#})WwDd>&=<;ooy^sIU&Kw z>?Q4`O&Xzs_{k;=s=bMuTs+%K*Cu7&f7q6d6`$d!@?PPss6?X~K=P&l^z!-RG-&Vjb+2~myOfq9#1K;@ zK5mq)>n|J44iF7dKwqY;hJ^6&qHgCYlICIVixn}41A&G z)#OTO84sBcNxFfeEqDnV^>YL}K!TP!w@I9)CCJQ)DuI>2<2SRvC_nv6>lsfazy7t~ z)4jhRwUw%WnMl_w#mOBZ$uhFPn=VUNG}8#_9JUP`;~oE*y%8S94k}B;6oYz3q(czz6X1b_WAjuff(XzH;k9GVW=7VA|T}%fg5!wU{f-ZDb@c&HQBiV^dcWk4`?qH zCHqt4hKP1lBpr~#IN4Pm$EV)2-R%QVNE8^oK)%aq{@x!ZCZH@mPAypIsK!taEbyOY zct9eUsxX4PPm{p`(a4JANUYHRl{ALgn6focqNnEA?Jqmk0KKUMO4Mh2W8-kVUIgZ5 zEceHA8{4#tSWt#YX4)_Hm^KGxZHZ*Eyw{Z-q8)BQ9CoU_fEAt+0Z_+mp8G%)HIz89 zGHuQ2|L|MXqU_$ z31k44cBSPQ?R@@P`WZ5RgO}88Dp+MRbQh!qeNnaz)ZI=Sx?v>}wEGXg=asPd>Gz?; zj=q-ld^KinYJ2jaa936IQJ-k?Dxx>}>A6`uGpTbrK(wh?hxjRomMqxS2D#{DE!rpF7!4Xb*IwkqtJvLN@6XS`!u{ zDKus1FvZ8NU-Dq~)*LjD<`sV4Z|r^QK*g!}ty0*D@^1G}Q}TW7aQxVK2m9t|(tQnS zr{!r>0fwbr)8hfXXjkdkbt&iIsg%B3$l4Fh^yIA6Z|~UV?k|f{^OAUNN=o|rw*bnx zjbBFwMEoXxw}VvV7EBH!3~_H+^(c{v70+tM@_u=I$I)fmJ53|I+~Pozac1IHN$jAD zd}e#HMo{#xfAl}8+YnIn!7HOL?%DxuC}&~&?5Z*}r19PQP?e*Z!a3F|s5+FRlHXoC&5d~#r z8giv8%_jc{N;;5vy5LuHP8xi6d(%-r{0}**dHGC7M=&6F`}!kAE0K)z2vu9o?wDH% zeFAcruN=F^cVZpK!}4aE$zHAaax9Nk?%?9R7orW8_4O-V3KSCM;Fi}tPNBUq!mlxJ z#H#1eVW+oY*T8nMHdVSX>ihTPVkLi8fiw8F=bgfH(mOG{EJq(NB3|caWzdbLN?aQJ zg1wFp^{CBfzEue=!oIg7Ig-vwy&4^G#v4O4o(*m_!D>C85~QaaaEuOCr=#huw=gW< z7O|l|71y##J}V+MiLN6LD^{j0Ry;x~=6IhCPVMM`Z~cXRraP_cVsQTuPkXT@2$z!c z`rNbTcXd5ENCT8CIuu7FJN~=V#8egGuYWeDqb15J3${stIUy6O8R?x<;gNHCRsC@m;FHSN@LUD! zCNg=BhP3Ymm*FO7v)a!ymzn?%8f+UJ_%8oKOZegi?|JxG?ym-98ma6IM^#M3q|XJN znW@2Q?A5%c;(NC;Qf#4!lY=VaN3AXQ-tp9-y|!DG-|RKwKe6t+2m6++1DZ?Bu69r6 z2DQsVTkoayx)Xd?3~eJ{o#(B*B+tE0`opl49)jP#Nb+{E4fXe`89&}-%K6wd{SGm7 zX&)s=-Ti6O(_0~i-$?>kMO469>lK?B<$o_g^wgGHCOug*D{XIxv17YqWBY!g4XDI0 zf`%LKr5cytS_EWHs*NRLpx@_*PrH%-ju(OC$e4oK17C~Pc|rBdghwRCJCAVaeOqwGf;GemqSK3GauV&lvs z7qgF|6(`+)6qF2C6RK;^jSNv}>mNm>Ob9!37m~n4#tjil>4`Vgp|kkMnuX5CWJh8( z@!zOvkM(=Kw#iLy|0}3yYzS$A=PsJT8^aooi*X_I`|@XqMPxW)>OEear3#ubG3Y_W z6tkK0Y3QlrLMf$P+t)1c7kv94p*KCLs=&%8b>pJ#LD2Ch`#FO~;5An({^lK%(nZ)$ z6e9(u;^5z&OV)&4tY|@ktR2@lOJ{v5$Vp&?+cJOd$?-C(XJhsRHz`=+ln88$cl@Kc zCWX7c*qR(dl3wgz1oEqgf;>-bS)TW+2R<-aZEoc;3=os4JM$KZ{WxmH^}fR}xSGrX zS>b+GBL*d(x>F7uvmWOIS1WF(QimLSdZeJ=C4ss6sa~BY*uT8DCd_cdQ66V$bO^c! z!@b00E=%kDI@`=epOW3nrrGQ0i23fSiBv61?O?D_WUdU@fC`&OCUkzhco&`iQj%AB z>kzJuhB@J8t7SsU6DHFhI54d9Kh-PLJCtqZOd@A?s>*_xCId1f&D8SZA1RlyKX8>- zFE5cV;LLx?@q$ktAz<#mGr7zq@*+**4D;CE^P=|4x|Z`?mTI5`R`t$^Q=bS7=dGv@$u#(KyT5P4pL*X$oc3X{+X3gsH()A~7dp0cSOd!QOh?lE5 z!g( z{q1Gw!ZY#Q6|}^IJFm+95_m*w-}3SpuuxCZ6eHTREULac4MYQTqGsKTzCWMOllbRs zMKP^dB#UB!ZvQ7`VULm&$nwRje z&~{}>+_k=?CZJd&5G7dA@TmuA`Z^vUIZ@@g5X!>>O*? zfdo-&94fkcsw|7IwSS z;M3ZAuP|kl7GCDsTl&$2E3rzt3!^&mQN*VV1_*`%rt93LslPHq(lwOKuyQ%}o!YP? z2v*_%tvi3G*;n_w1yo@h?_%T=fxS}Jbcwx7tBPfV&wT$mtf|R)qZ;B@z-zWLF?>^_ zt-E`k?G_~$u828=Z-;UC2j_q88X&p~tb$G^m^SF`Rl_^`e-rs01o4SHI9BV0Z|2M_ znG9D!!eXBN$b#rd=*p+W{iD+h5d3$i@)^HHiu726-8GkNz9FP9qPtL!$E9ZSw{PWc zSkSP84Jsth*|gB|$^^godLV_$B-fa$(+cWcBS_z#bK{_jz|JAaqkQ@wqhY0gs!X?r zWR?6Lz$nl!etrGG{8oaH0q}RQN6C`dq_Y){BuVT~)si4a8uels62xs&T!%gmP{W}vSbupj13skh01;j0!9CPVJj}Plyi?3Gx?!g?OiIw9Z!)R}eF*LCS z0^IC^cTT6fP03+A8s9u|K;GHZ!3IaJWgC)s>@A3ZjM2A1Kv*rYU&@5rBpq_N9^w-n zu`pk=gAsu*)c&SM{SYNx%3-K?IvQXVvqG!+)&;z`px$|=09Ol%u_b*-e4r(~YK9`9 zOb@5%4}YFCGzy$5w(wA%;qV(M)V>u|0JrCTtL7==bLK3b# Date: Tue, 14 Apr 2026 17:16:58 -0400 Subject: [PATCH 3/5] Add adapter-hypershift-nodepool chart and fix adapter-hypershift for OCI Add new adapter-hypershift-nodepool Helm chart that subscribes to the nodepools topic, validates the parent HostedCluster is Available via adapter status, creates a HyperShift NodePool CR on the management cluster, and reports status back to the API. Fix adapter-hypershift task config to use Route instead of NodePort for Ignition/OAuth. Add values-oci.yaml for sentinel-nodepools with RabbitMQ config. Pin sentinel-nodepools Chart.yaml dependency to v0.2.0. --- helm/adapter-hypershift-nodepool/Chart.yaml | 11 + .../adapter-config.yaml | 26 +++ .../adapter-task-config.yaml | 206 ++++++++++++++++++ .../charts/hyperfleet-adapter-2.0.0.tgz | Bin 0 -> 21030 bytes .../nodepool-manifest.yaml | 28 +++ helm/adapter-hypershift-nodepool/values.yaml | 67 ++++++ .../adapter-task-config.yaml | 11 +- .../charts/hyperfleet-adapter-2.0.0.tgz | Bin 21029 -> 21029 bytes helm/sentinel-nodepools/values-oci.yaml | 23 ++ 9 files changed, 363 insertions(+), 9 deletions(-) create mode 100644 helm/adapter-hypershift-nodepool/Chart.yaml create mode 100644 helm/adapter-hypershift-nodepool/adapter-config.yaml create mode 100644 helm/adapter-hypershift-nodepool/adapter-task-config.yaml create mode 100644 helm/adapter-hypershift-nodepool/charts/hyperfleet-adapter-2.0.0.tgz create mode 100644 helm/adapter-hypershift-nodepool/nodepool-manifest.yaml create mode 100644 helm/adapter-hypershift-nodepool/values.yaml create mode 100644 helm/sentinel-nodepools/values-oci.yaml diff --git a/helm/adapter-hypershift-nodepool/Chart.yaml b/helm/adapter-hypershift-nodepool/Chart.yaml new file mode 100644 index 0000000..018acec --- /dev/null +++ b/helm/adapter-hypershift-nodepool/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: adapter-hypershift-nodepool +description: HyperShift NodePool adapter - creates NodePool resources on a remote management cluster +type: application +version: 0.1.0 +appVersion: "0.0.0-dev" + +dependencies: + - name: hyperfleet-adapter + version: "2.0.0" + repository: "git+https://github.com/openshift-hyperfleet/hyperfleet-adapter@charts?ref=main" diff --git a/helm/adapter-hypershift-nodepool/adapter-config.yaml b/helm/adapter-hypershift-nodepool/adapter-config.yaml new file mode 100644 index 0000000..1515786 --- /dev/null +++ b/helm/adapter-hypershift-nodepool/adapter-config.yaml @@ -0,0 +1,26 @@ +# HyperShift NodePool adapter deployment configuration +# Creates NodePool resources on a remote HyperShift management cluster +adapter: + name: adapter-hypershift-nodepool + version: "0.2.0" + +debug_config: true +log: + level: debug + +clients: + hyperfleet_api: + base_url: http://hyperfleet-api:8000 + version: v1 + timeout: 10s + retry_attempts: 3 + retry_backoff: exponential + + broker: + subscription_id: "adapter-hypershift-nodepool" + topic: "hyperfleet-nodepools" + + kubernetes: + api_version: "v1" + # Use the mounted kubeconfig to target the remote HyperShift management cluster + kube_config_path: /etc/hypershift/kubeconfig diff --git a/helm/adapter-hypershift-nodepool/adapter-task-config.yaml b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml new file mode 100644 index 0000000..b4e86d9 --- /dev/null +++ b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml @@ -0,0 +1,206 @@ +# HyperShift NodePool adapter task configuration +# Creates a NodePool resource on the remote management cluster +params: + + - name: "nodepoolId" + source: "event.id" + type: "string" + required: true + + - name: "clusterId" + source: "event.owner_references.id" + type: "string" + required: true + + - name: "generation" + source: "event.generation" + type: "int" + required: true + + - name: "namespace" + source: "env.CLUSTERS_NAMESPACE" + type: "string" + + - name: "ociAD" + source: "env.OCI_AD" + type: "string" + + - name: "ociSubnetId" + source: "env.OCI_SUBNET_ID" + type: "string" + + - name: "ociShape" + source: "env.OCI_SHAPE" + type: "string" + + - name: "ociOcpus" + source: "env.OCI_OCPUS" + type: "string" + + - name: "ociMemoryGBs" + source: "env.OCI_MEMORY_GBS" + type: "string" + + - name: "ociBootVolumeGB" + source: "env.OCI_BOOT_VOLUME_GB" + type: "string" + +# Preconditions: look up nodepool and parent cluster from the API +preconditions: + + # Fetch nodepool details (name, spec, status) + - name: "nodepoolDetails" + api_call: + method: "GET" + url: "/clusters/{{ .clusterId }}/nodepools/{{ .nodepoolId }}" + timeout: 10s + retry_attempts: 3 + retry_backoff: "exponential" + capture: + - name: "nodepoolName" + field: "name" + - name: "generation" + field: "generation" + - name: "nodepoolSpec" + field: "spec" + - name: "nodepoolNotReady" + expression: | + status.conditions.filter(c, c.type == "Ready").size() > 0 + ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" + : true + + # Fetch parent cluster details (need the cluster name for clusterName ref) + - name: "clusterDetails" + api_call: + method: "GET" + url: "/clusters/{{ .clusterId }}" + timeout: 10s + retry_attempts: 3 + retry_backoff: "exponential" + capture: + - name: "clusterName" + field: "name" + + # Check if HostedCluster is Available via adapter-hypershift status + - name: "clusterAdapterStatus" + api_call: + method: "GET" + url: "/clusters/{{ .clusterId }}/statuses" + timeout: 10s + retry_attempts: 3 + retry_backoff: "exponential" + capture: + - name: "clusterAvailable" + expression: | + items.filter(s, s.adapter == "adapter-hypershift").size() > 0 ? (items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available").size() > 0 ? items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available")[0].status == "True" : false) : false + + - name: "validationCheck" + # Only proceed if nodepool is NOT Ready AND HostedCluster adapter reports Available + expression: | + nodepoolNotReady && clusterAvailable + +# Resources: NodePool on the remote management cluster +resources: + + - name: "nodePool" + transport: + client: "kubernetes" + manifest: + apiVersion: hypershift.openshift.io/v1beta1 + kind: NodePool + metadata: + name: "{{ .clusterName }}-{{ .nodepoolName }}" + namespace: "{{ .namespace }}" + labels: + hyperfleet.io/cluster-id: "{{ .clusterId }}" + hyperfleet.io/cluster-name: "{{ .clusterName }}" + hyperfleet.io/nodepool-id: "{{ .nodepoolId }}" + hyperfleet.io/nodepool-name: "{{ .nodepoolName }}" + spec: + clusterName: "{{ .clusterName }}" + replicas: 2 + management: + autoRepair: true + upgradeType: Replace + platform: + type: OCI + oci: + instanceShape: "{{ .ociShape }}" + instanceShapeConfig: + ocpus: 4 + memoryInGBs: 16 + availabilityDomain: "{{ .ociAD }}" + subnetId: "{{ .ociSubnetId }}" + bootVolumeSize: 120 + release: + image: "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" + discovery: + namespace: "{{ .namespace }}" + by_selectors: + label_selector: + hyperfleet.io/nodepool-id: "{{ .nodepoolId }}" + +# Post-processing: report NodePool status back to API +post: + payloads: + - name: "statusPayload" + build: + adapter: "{{ .adapter.name }}" + conditions: + - type: "Applied" + status: + expression: | + has(resources.nodePool.metadata.creationTimestamp) ? "True" : "False" + reason: + expression: | + has(resources.nodePool.metadata.creationTimestamp) ? "NodePoolCreated" : "NodePoolPending" + message: + expression: | + has(resources.nodePool.metadata.creationTimestamp) + ? "NodePool has been created on the management cluster" + : "NodePool is pending creation" + - type: "Available" + status: + expression: | + has(resources.nodePool.status) && has(resources.nodePool.status.conditions) + ? (resources.nodePool.status.conditions.filter(c, c.type == "Ready").size() > 0 + ? resources.nodePool.status.conditions.filter(c, c.type == "Ready")[0].status + : "False") + : "False" + reason: + expression: | + has(resources.nodePool.status) && has(resources.nodePool.status.conditions) + ? (resources.nodePool.status.conditions.filter(c, c.type == "Ready").size() > 0 + ? resources.nodePool.status.conditions.filter(c, c.type == "Ready")[0].reason + : "WaitingForNodes") + : "WaitingForNodes" + message: + expression: | + has(resources.nodePool.status) && has(resources.nodePool.status.conditions) + ? (resources.nodePool.status.conditions.filter(c, c.type == "Ready").size() > 0 + ? resources.nodePool.status.conditions.filter(c, c.type == "Ready")[0].message + : "Waiting for worker nodes to be provisioned") + : "Waiting for worker nodes to be provisioned" + - type: "Health" + status: + expression: | + adapter.?executionStatus.orValue("") == "success" ? "True" : (adapter.?executionStatus.orValue("") == "failed" ? "False" : "Unknown") + reason: + expression: | + adapter.?errorReason.orValue("") != "" ? adapter.?errorReason.orValue("") : "Healthy" + message: + expression: | + adapter.?errorMessage.orValue("") != "" ? adapter.?errorMessage.orValue("") : "Adapter executed successfully" + observed_generation: + expression: "generation" + observed_time: "{{ now | date \"2006-01-02T15:04:05Z07:00\" }}" + + post_actions: + - name: "reportNodepoolStatus" + api_call: + method: "POST" + url: "/clusters/{{ .clusterId }}/nodepools/{{ .nodepoolId }}/statuses" + headers: + - name: "Content-Type" + value: "application/json" + body: "{{ .statusPayload }}" diff --git a/helm/adapter-hypershift-nodepool/charts/hyperfleet-adapter-2.0.0.tgz b/helm/adapter-hypershift-nodepool/charts/hyperfleet-adapter-2.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..dbc44e3c15c00354d170f27d88faf2a6b10e0470 GIT binary patch literal 21030 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

XQfes z5+)vQM2erjrZgImu!#T`74Isi*c%GsjWCRyI#n|H6tRWfQGX(TM%-ADb=t*CEabdP z)QYQgKx-4H8}$}ZmsmE|wZ?9>xzvdOT0z6jTset&4VOr^#zJ)yht34@GtH(HrCw+! zJcsYN6Eg?zAbF9eMJ}|#or8Nmz{=5ulhrS1CZPtKFw;@5L6(mp;n!$0fv(-dA_?;{ zu8pw~6ZKBWZ|VwE0{Ez^x3+t|XPw?wr?-8+wcGDK?e}*7tM|O$>$RM_(FUue6LoVN z0LCG@Afa)L*&UmJ07Wm8tjU zFYbkdu_`=ycUERKSYiyQ;||!!^u};4<(b(Ew29W|Hc5Sa9`L!b{kJtsso5jVZDU#d zPx;Na|9-ah^!cOx_d`4tBffJt`>zcgWg|QWDso=mlc+lx9oqc3mBhg4tr0olD81H% zu~}8NeAgLCh3kP-8fmWSl)u&=b;^jndbhSoT{z6Y7G;PleaA2;gz^`Q`G|%3%p+kI zHoeo=86AFI+PYz2teBDn1Wk!{Vc|nrHKbW0R7zostRIZkyK~7GtU<@&z+9ECEZQ4Z|zo{y;Mi*N`^5DbCr^&&?fmE-w;Rz4KvnE#;V8$%t5On&qfd-R1XHV z9;4LK9?+0as1Kl&p4SyDyLIdE5g4G#ntWpsJGXtdVulcLeQ3~uinFLToHWNr&pOX|QPs_6 zQEpps=^eH!oBw*!ak@j7-n{o@sx~|e41kXOh51|)+u9HbfEQ`XH-h* zN!04YKM2dskzXDPliYRu<8nZ%)d+?x(=Ln%<|PTQEEtU)Hkr_fk(|b}4Tzm1Eynq! zWh0`5^DaxrBx&{Gh>~f=d(TwA9@C`e1gZOlkkS$hCWxpzPP1iymywV{N;Ae)eB6CTI)vHP;Gx{Cdqi>rMEgWaQCe@q+ znIlfof@9uPz2eE&xhiR)31|-qi$RBy!dGUp%*r|~V(R5-)kypM*}JzVBp+9&&!P1I}qSVF~IctJkb4 zW}j;r=L`kAxn0+dVNSbY#UpZYV-CPMEo#l?xjAIEbc^U{@8q%~)?0i|i}nt`4l)Sf zlU~TJa{qIut%IAD_qP@p*h6pMSUK{|^8F|NqY75G?@I0RT~)^%ejC literal 0 HcmV?d00001 diff --git a/helm/adapter-hypershift-nodepool/nodepool-manifest.yaml b/helm/adapter-hypershift-nodepool/nodepool-manifest.yaml new file mode 100644 index 0000000..5cd1701 --- /dev/null +++ b/helm/adapter-hypershift-nodepool/nodepool-manifest.yaml @@ -0,0 +1,28 @@ +apiVersion: hypershift.openshift.io/v1beta1 +kind: NodePool +metadata: + name: "{{ .clusterName }}-{{ .nodepoolName }}" + namespace: "{{ .namespace }}" + labels: + hyperfleet.io/cluster-id: "{{ .clusterId }}" + hyperfleet.io/cluster-name: "{{ .clusterName }}" + hyperfleet.io/nodepool-id: "{{ .nodepoolId }}" + hyperfleet.io/nodepool-name: "{{ .nodepoolName }}" +spec: + clusterName: "{{ .clusterName }}" + replicas: {{ index .nodepoolSpec "replicas" | default 2 }} + management: + autoRepair: true + upgradeType: Replace + platform: + type: OCI + oci: + instanceShape: "{{ index .nodepoolSpec "instanceShape" | default .ociShape }}" + instanceShapeConfig: + ocpus: {{ index .nodepoolSpec "ocpus" | default .ociOcpus }} + memoryInGBs: {{ index .nodepoolSpec "memoryInGBs" | default .ociMemoryGBs }} + availabilityDomain: "{{ index .nodepoolSpec "availabilityDomain" | default .ociAD }}" + subnetId: "{{ index .nodepoolSpec "subnetId" | default .ociSubnetId }}" + bootVolumeSize: {{ index .nodepoolSpec "bootVolumeSize" | default .ociBootVolumeGB }} + release: + image: "{{ index .nodepoolSpec "releaseImage" | default "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" }}" diff --git a/helm/adapter-hypershift-nodepool/values.yaml b/helm/adapter-hypershift-nodepool/values.yaml new file mode 100644 index 0000000..cdb8816 --- /dev/null +++ b/helm/adapter-hypershift-nodepool/values.yaml @@ -0,0 +1,67 @@ +# Values for adapter-hypershift-nodepool +# Creates NodePool resources on a remote HyperShift management cluster + +hyperfleet-adapter: + image: + registry: CHANGE_ME + repository: CHANGE_ME + tag: latest + + adapterConfig: + create: true + log: + level: debug + + adapterTaskConfig: + create: true + + broker: + type: googlepubsub + googlepubsub: + projectId: CHANGE_ME + subscriptionId: CHANGE_ME + topic: CHANGE_ME + deadLetterTopic: "" + createTopicIfMissing: true + createSubscriptionIfMissing: true + rabbitmq: + url: CHANGE_ME + queue: "" + exchange: "" + routingKey: "" + + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLUSTERS_NAMESPACE + value: clusters + - name: OCI_AD + value: US-SANJOSE-1-AD-1 + - name: OCI_SUBNET_ID + value: CHANGE_ME + - name: OCI_SHAPE + value: VM.Standard.E4.Flex + - name: OCI_OCPUS + value: "4" + - name: OCI_MEMORY_GBS + value: "16" + - name: OCI_BOOT_VOLUME_GB + value: "120" + + # Mount the management cluster kubeconfig + extraVolumeMounts: + - name: hypershift-kubeconfig + mountPath: /etc/hypershift + readOnly: true + + extraVolumes: + - name: hypershift-kubeconfig + secret: + secretName: hypershift-mgmt-kubeconfig + + # RBAC is for the local CLM cluster only; remote access uses the mounted kubeconfig + rbac: + resources: + - configmaps diff --git a/helm/adapter-hypershift/adapter-task-config.yaml b/helm/adapter-hypershift/adapter-task-config.yaml index 98f07a1..857584f 100644 --- a/helm/adapter-hypershift/adapter-task-config.yaml +++ b/helm/adapter-hypershift/adapter-task-config.yaml @@ -36,9 +36,6 @@ params: source: "env.CPO_IMAGE" type: "string" - - name: "workerNodeIP" - source: "env.WORKER_NODE_IP" - type: "string" # Preconditions: check cluster details from API preconditions: @@ -118,14 +115,10 @@ resources: services: - service: Ignition servicePublishingStrategy: - type: NodePort - nodePort: - address: "{{ .workerNodeIP }}" + type: Route - service: OAuthServer servicePublishingStrategy: - type: NodePort - nodePort: - address: "{{ .workerNodeIP }}" + type: Route - service: APIServer servicePublishingStrategy: type: LoadBalancer diff --git a/helm/adapter-hypershift/charts/hyperfleet-adapter-2.0.0.tgz b/helm/adapter-hypershift/charts/hyperfleet-adapter-2.0.0.tgz index 25580d4c80c183d8f349951a68a4ff3cb4e81e92..3129398a8b20c8b3648c89c74c5676dea1b58e0f 100644 GIT binary patch delta 18693 zcmV)nK%Kv(qyeR*0g%9dPqw$8?QH)?Z|m{1XTAS`-W|2S@F_%2vj6Dax~=Nq{zV>= zvL9(C7*F~z-3~~a+Mm5{uiNWHbQ(ldgc(b7eEnKd%qvMTu&XErIr)2WKG8&CZE;|(fBt-Vt8P^#llR&*~=v_WbX&*?M#w;WfmBG|tdRt;T z_$>3^u>PkcE~vPFLk6&T{crbno;zx<9YT`8fI^s2lGCUktU(7j@~N$VJ`6KH0g?cH&qU6W5h!>^ zB!$5Y1kLefXkNY{6Y2oiHUMzAI7qjeTpdp79a~z^9QY8+3%3Hk2#7nfegL}beaN$d z1^{bi_siZ&mI7@ygoq7?G?NIx#v(Oy8$gl>+cm(3Bo=gCg7Ze#hgK_a8rkkwFpVQ^ zD=5Nn&5nY9f&2h2=?qv>_6-|BWc2YbQ%V0aOmZ@_grwhF-q-4Nx4J#AUptNcQW#B1 zcu7Vyf-9DfAs5q3?a5@5v;Msn{&}{$@i#)fIP}J3auKOkqkg0fnHE zoY$O)OGrsZCL#c5#q6d`c0q!8K?H?&Cx_MjPr_?|zyA4n|K!!-{{Gqd?(xC-i`~=x z^LHnQ>sWU{1bvsqYB%g}dYfI!`rr0?J>7A~AK>l=nr`Y_!JJnZA| zn2%JKV>+dAA6PQvNP zf3md>ij@@q>S!?dW0U5gX`^vjs~S%0^=kF56*(6niCKb_LK8BGX{2X20GN>XCloh5 z(T6R$C?_m&-UI+S$woBaJAU^rXEFOH?uW;JGz)2xlM(I1w>?|vh)#Gmn_q4XUS#}| zW|htL%Uy+O##5T*j0z}{h-NUr{&z>5k7Ak@15pe%%*%`n1}vZaz5z53yMcPUXno?} zi&s~U~Fh7W}^YRVCw!ehatWzkWBs!$IT-09iHP|h3 z-a%|ivKhu?B&2ku_XebC=t%aT5s_y_m=_s^Ng;AbGulB4TGyaXX)cE*rv$e*d8mqc zib7gWk<$KCB0xiBi5yK-pcJUMi3DkX&wn&3nH095<{SrbFg#*H$Q5Xpo%!w}zM+^i z*|LvjD&-gRx8|5d7Hd4M0sLOjf|ft&`*2K>QNx=vJkqsrR`jQg7jo=B&>0qQ;ASD8 zOh^(1`d`JKBqN@&n0)I3iOmE}C-aUah1j+-j}wf9%IqHesQ?i%bTQQDx= zEl#l!uf^q!S?*{s&`@wf(jXWXaV)ipxsdyelN(Z%QzNFxnMbO zEVil|NbaU1*?(Ar6Z(6>G8)1E`#=8|5^fe@7x~we5$JRzcgC)Fb257VH!T|fT@B~B zh~rbF3WMu|gyrQ55rLF{TpvmlK*KSIE5a1F6)~kLY|AeR{pMFmK8I{b!&w+p z7;}Cpg4q1lqE?#G)0}JKpzmpD$iy-yvpzboIbg}A7&~t|q4Pt3wZ7F;%}yyE6Vi-J z!NikR+9~x;LyE6;EH49*lO!Tpv>vR%J3$5f@U1WtClpQ@iC@x~%uZ>@lSn-8Z9qyh z#v}7$tG59;n^0cl_SFUq35$!2o{cjq#ypOm?*y^#>apypC*o;oL-NV*X|8u)D~yLBYH|>8sM#%M!U%o)@ zO~YIOnGzP00gG8agQ&=)XeZfCl9Ox(k*zMEEn5XtmePcOBq7}}+FU5oQAQ#v*1Lfc z4<;ii+AN<%VDWe zHO=0d*9#?(`wgjgYfvO=R%gdN+MhBMR16b47`|5DO7!ta$$tXcEU_DdlJ?Y`=`k4%+);Y9UfV&zUJ1lcE&~{iqu4Z;5R;id`xmBPZnuJatcf>_q;%u zlKW@KTopD&kPS%Kcmg;v!lJA<%7{FU7jX$Mm?a^pImUxOd4TH_Xq?o8{hkx*7mnY4COiMY)(9m zKr1UwX|C6xlw{O#iO2DPgqJ}^)gkRQ6H+ugls!Xjhp00$636`NIAc>5(-Ga5{F?Ia!tF4#_g zq{K9#`c@;*3vX8~JheuhT@pz>LW-BXPL~F~PUUziJ1U-iQyQ`%qXMqR6t^W(t?BIO zdY^CNVmKR;WAkBC^Rickpj|9`Fa)__qAnqcG%OU&vx06&sX_u`0?I{O$kQ+aHqt%h zbdts-r}p|1cY#wD(U4@WSS!@4xst7a{_cvj$%rLVpxyc_zD?ChbWKld?KCO{#nYT* zd672rk3!I45v%<_=2smd3u|SS?o=b|kh7AQSM_OO~cI>KZMZ(xK%Xc$xz* z@~&ZPx}|2x_f$?0Rg&72!~xhpC0z)&jNU?_jl$W0LM(Und`uEsM{fRqIOX(A6_z_d zO`y;+v81R9k>wmFQsi=xZ$xE;vahZT`jF9>5<&ZtE9jDPKP*LPyq@EU6|t4lSz=SE z(UGZ7Ig3!gdX?e%Sgv$g$B5zD6Th4Cs(&5~Cw3aF++I z4LeblQTeDZUEUZWusjfdNUVez7ouY&L@4RqLxL9G5Mv0*UZ{d=+tN|nqUPoZ(K`~# zI#vOh>)l2*d^M&StxggX_E_4EoOPt9q^!QKHl?2=rxjAxCiNb4%ibG_x?D3Pity~q zlH9H~BT!e`0>NFP2}xo(6OQTB?wS&}j`ZDi@q%03=eD-^@}34E`yV4Ch|TjcjZ>P5 zZl2!86ljV4&*P_$w=4EP&vu^eJhMg)5eR>05B9(J@l;u!GvnIcL)ns*Q9_*}o73&! zk|j|eUh|ColP5Wek9oAKOVBKs(40ghCw=R1w#0zVx$0KRPVM($6|0LX$JR`+qGJ`Q zQqy9u&d^B&qk>4F-q_MqZ0Z(9-A9-hzoEZqNXbb^i>yYJefz>@ucYv)PD z|7Ux9`|-p6e=pCf7A6%TV7jF@erta?cm1t?zCG?;ZvASDubRS#e3J5nCV3wk!cqoi z980l-_9Y)bYB$)XNSYXqsu~{igS#t9L>>resr$Ru3azdqSA({*78^SSL zs|(b8%t$h#@YN-qZNOKlF=1F@fTN*dHTd|kL=k+|MdP9F_cYhwR_au3r~7~0EA7jC zuE+mxb_!g||F^2=zpb6#_5=UFk7uFE>yz{TFM;o`#Pa8}d!N-e#vQ*7w_nQX?}E$U z2Zz6B?*72uZ}zOn|Fz=sW=g;%{Qq&U&i}U_KYif;_wh7q#(kH|uP~?=jm~DNYcS<% zyIasQdTe`<@KvdWmz5TAaaVs|-*xU*ND_Hq&>p6Z)=$n`uYc?7C7Zz8mA3J=aW(tK zz=S909{06?_K4^3_3!H~X#d7p0<8^bRUo^KR{e19dV_3qOS;$nQT3nn@Q%yfq^f_=-MvZvttvR) z;qy14c|CV!g~qV`7)-zIlwJ=2oS$-+boPH2><5@r*RxUOO@^Gx1RblIwfKa9^4b^M*E=xgG?OYmw&ap083cAs% zqz-qE@y3ean~gSXD;j_JpqyBhA|#G!%)~?mrgN_=tF+^>b_~CYFOL2kCeiCZy>zqHi9RW`uI^8eP8?Ov~X|G$6Ldx-yaFV8ahv1+bg zO6Mz7s@g7hD@Oyhd!T*xvJ=9xxD6#}PycXj*o}_yHQ=TC`lb!k{e8PNLkn$;P%}jM z!i*{$oBt(dB$(i%ZqeYz!Je~Up{tgS!=nT4s?TotUC;Nf;mET{Y_H^8a4a|6L<~;1 z8!Ieop(&2GK}&yAgl!K+^Qw9cnn+S6@^BuG51pxAKH2qvTO|&ksy58_pxbb05s#Ek zylgAftf*wRGVz#(m!dFve?0P5r+N6Qd#1b5eL)08-^kXS`-;{&4nvH|_LHZgn7B<< z>_p~ww(&-^)?w$S?-so-Z`p6qb#JBkDM6}58`jCU3tNAPJzBaQHPu?Gc3f4uWuFa? zYHXyM;irUC%l&hWKGd*Qk9xM0VK)Uz>-tSNBw9=EQI>4T`{8hv1r=+fsI<8-?s!>i zNqBOnFDn9Uz-}!EL4Br5s;cB(nLmX-rFco696IrnKi!5(!Uiy%a(j29KRV>TVEl7% zAm$=qYukT^ti$@ZYC#pPr{u?vn++eWzv0J^{l|b=ZJJE$z`V{_9qJ95oYQDY z`G;@6KR?|6asRLaUndHjdp#QSQ5Ulr&jUOs{qlb`n`!pAXi)1cXj*4!zE#W9dN|Xj zmS3E_{h`dd`0C(rzqwV+OQ?=$n~p}WHgUJP;i-);IE>#ol6g^(EW-E*aqnT``!M#Idpat zJ?(#7G*ko>FR`D02sKYF_g@9r0&@1^`LrVz=IBH%e>HZ%Mw68?e+ybB!%VCEz_Yxi zqxG!cWQXt$nr^bI=E2t!V%_QJeekv6tHfHLX7p~xpXRYwa{}Gz$X(!{c-p;$VSd?) z)MraBX~NstZMM+rvN1_S?@Y>=x(Rd}nMr@eS_{0Vno_sS1nWMP?7B^@)bG6KH?m}Z z#A`QIe14GyU8>7j)l9=pT=AwdS6#Us}t~@HQB->~QN>>}aW$QvjS#Q<@F%^@!%J z4QRzo;J;Urv&lac?&V)9Mwk36H6J4y)10<`Z46}X530~^cXJM7+ZEP#f4hl(p&}uF zG4su1s>A!3`B^Zcz=Zm7Dfedm7ljH^)<-&0P)|Kmj$mbV$wCtlC#!Kam7hwPys~Zg zLjJG&aN8|l3IFds+kRZn|J!@u|M&4U`mJf2{ZiJmIVUKW2)#@Xjswrwq0Zf#tt2{S z`UJm(qt(KYO2@WXQZcFGe>o4i_D?atTn~)1y<_LfwKU;z;Vh7HZzIw^zOGJfmG!SP zecqT2EL;Ciwx3n)|DQeh|K872y*q78z1o@Q;9A+-uFWqNY;t$xixf5|NCTnr?&o|J;eXIm#0CCrDy#}f6uy6SGK!*BqakD zvz$@U-90|g{6KVH69FwvB`g_rL!MEN=9rt)t<6&pUTeLov7A@Y=P|na9mpijEYA$r ziq<-Dx~fwVxUW<756?S$70JCK9o(6_|2$r3`X751_lxJ z{!5nRG@Fum5v`jtrJO6mjHL8TOjzv`8C!~TD=zx(oNzdMQUs*NT7zfYe$t?GXt@9aG6fB*OLtifwZ0k0$ln2=^p zx}zXy_}qd0DNXWDl(8vIY-au;&s<o4DB@%kH3pz^!oidg z*gZbz2Ep1Ie;j8Nvz`f-QxOCm7tB_7>#u(aItnp6-JbkgcJC*iUB;Y5a3EO&%V*G* zSkS!&g82j25&S_SauOr+$;Z@S_u}GWK*Ts0OTbJOf%=++?q(<}h)w7W%OCVTnWQll zn>vRB+C5!dTwsIm61KcarBQBZ(Sq?Kd<1K2Yw!b|f59=y$b{xJ6G8By`~@H2CAF6G z_)958J_H{+<>UVC{`wGnfD6|h_yRt_n_@Dc8StU?9)=Hav2_7h+ILBk zp=)U$eiew{ct&AvTl6C+~u#RR4m_|6EG{R(;TE{>tev)cGPcxp%Mg}B= zlx7$fhbMPWbB%frM4jqg1u-AsBF_q{(Oju4e`PsYwNeMQe{E9ayfdN+%`gug8>-}; z>k?$Er2+c)QWDl{8mEevrZmfE5^p0mrLcGM?xlkmoKyd}P@q%^LTfChy?p9F~ch=qQ2 z$HDv2MG)+haBNb~ge1XagoxEh?N%fK`ko0Scx%%N?F%pQ^u;>xOcQtngBhX{_Yh>@ z3KC0ViTw-7yf0+=3yhh0(G7wZ^onE{f1u9jWN>oh0tsC%5T?S2$&GGEGn&Z$u|)Rn zqK1KsE}V_2pt^>HEKgBpJH-yl&TXJY(u@~4@Ip`+aoYi0ve=0!g=m(L2@6etelo=L@+hwt%GYebi!9g0_|u=P&3A5N}H1!(1S^f5P2G zRu8vYum1Dy(IGnjQg>O^Dl3|3uC3-XCE+C*(MT~)MPMJ`;*XDw@cay0Hp>+98b{(g zJ^=X%-klsmdydbf;$7oBPy3r*me7>-zwPyU%>$}*KzlIa2#*iII5bw<)XtKs$>~-z zXzTpx;7cxfP#&SODocjEQn?o4f1yq`PH6Wfq%$uSz0;#cVDm_*5|_Oh%ra7{23=nO zWd)K7d`c8Ws$}bnhz^R;xf=F`WET?klJgCvgfpR;gy@GtBttbtBnsO48m$i(LlO&m zvEGG)99a>G1vgn?k##%7@pAvgyYIDUtKy=a4t7L4FV&A=!R*5gnRrB@QuXOlU{FPP8QYjDifBhqOw#uwYIj8 zSgo2oQBvVIq~GDef4`|ITc_5TTXXwc)f{5%m`BxZ;EplpM_-bCl5M&-&aQlBLmsJZ z*~U6fd8Ds^yyC7$8EQ%7RwK%riL#{Qq@~tQ>B#i~E`G7f0_`@2%^RmOWk={iTNI}U z;g<#Ekus$6K*>1jDRz1m{jbpo9MDZ#Kzm=`G7e@2(|TjDe@92*Y_p-#B4&6}q3=^J zH*LWu6kea59hX-h5}cMN?*6FJ7-7H>8qIe=2k{C+Z+6jlYTE*h)th|Woyz)iSyhQr zF6ozc`JlZ*J^}g9^L=r;di?T**TFdzUAG8a-7H~~VgjX~L&_sbshtHdJjV!xLf#{k zB58yZtgXSmf7Y>T@sS7?8eOjD8rRq1{&xUN^|-1~N6Nasiu1YAW=>`u`De#!I?EcK zl<^$BjA|d}NCeL(v(6|?J4OsxT(2Sl$ja`kCAD!smv!(Dt8(6V#I_%8Sa;+Gr7B47 zzbNI2W;st_qS6V;SxDk|)|cdMfPwr|1$f`%iQ=?Ae`SAYAaJCsIcH#0qGfz?jbpZw zDpoA6RjT&Ys4_wo&b=5*$`jF-4N-cIU4I)yl%gwT%?Y&kdRUj^w zIv2NuLg01aYbn!0l_c4G*>g(sA`OBwj#@J3zDgYnYZW$tz(ty|BxEUx@oY@oYo@A} zRi7M7f0F^Z395_~_(AL5BglAc^>1nv>m^@WgDrD_Gm^emO@lGq8-*zPGcqxK=;%HO zqn~eJXlik5M)CaU=kw!}xBuARI}3s}*zUsj8A)=B5m$S!o6|J~kSOXfGU=$>kIq23 z6F>=3H#Lz5*6ex9qOiZY$;d=cA4!=q9Wrn?f484K+1~lTy zNW6Kar^n3BG3T+^9ETI%fNlf4dvql_9_q`@bhoww_k~zqTIof8NhiUfz^~4(gyHmf=xi ze`X?63ebe%>+aMYk=mEdO>=|IfYK{PY>1kw9P*4LBIQ{Q>Pkm+E7H~ncxij^0nTjk zieNfd9Ic3^!3X%|qP&^hy!f@<-E@Ah!v~iW^CX^G<&0xSpwSY~3~!cPCAPK>YLxQ5 z154josV++g?yVFe%6sYfl!E|j)x za=(kTPT09NhJ%;t#te-atoaC5_U~FZ;DpCinS40@X{v`LFE>=I>vZiOd|))Xf67Fp zqbx>f209>;tdYQ*5lh}XA=xWB-fsl(tHfz0c=YH8I)fo4d67w+(Fspf;PR;d=#fOg zfw~5q==l@adHO!5Swdo^o3(0Q0qu*74*M7DK?jaAJbN>>7_s-SEmOzK6e$2JczgSY z^0=H)y%BfHDUX;4`IKfed1gkjh1` zC9T(rx5^_LvniEgphk`IUut9$qm$#kasg_^xd+X1d*ZA}NeaZ#GV?^spYG6t4!ofv zm*QxhsPtuZUTrR;qKNZV7Kj-_m3>sMlG-@Lzn~&$9jiI5*=uy9;5n$j%cQDicAhZ<>(y2fDU=)9`U^^J3P;z zfm@kSs-A|-O+mKzdiTxu`{ze0_-N(SSpBRMh50WVGKrN1w6Vq;QD-HaFX}7L^`kN%tzgrCxbcRsXHT_Nh_4fv? zQ+eY2oBi|e%QaGei|CZbJf)dcO8tFx#nay$wbQa{RlO6|bzd$6pj1y-4gXwLzD5eF zZWRBn>*KV1ZfXAdf6t=xU(=p>lQx#vfA4HL53wL z$c)sDl-cjKv4X=sE;RQRXo2RYGjTIwUnu^sS`k{LN$8cM=)%p>=)TYZZnbkjMT541 znlWYiE&tG(B~hLP+*YrD8dv})!6QOcJ0jrjSPqm3&p3qTe}cwCWbbdMaxC;pquET4 z81t);2x_b)tLKJCkNWVE4q2j)Y|eN}dCn2g{KK2+hnzAkSYa#sa}FZ1wUhE#VkIia zh)9YR|KNj-IcGHIe5MX;PNFN>vUj|Z&Ap;|IL2eOq6TftZz-a3_|Z}Y3SJbWlt`#` zxeh_IBod{Ge@#jMN))djh8{ht3S<(@i-N_Guv7ynnV}lEQQ~vm5*7$^vMo!Gn)=jK zTR~a2D2wRh*D}&oGMI9Z67U-zK*%M9DTAye(sG&kWTrrkx^k|QJvlW7V2+Z9iX22p z6Ou80&BNcLM<*|K_k7{LHemA=4@%{gIo9;J7Yav;e^KG$!ld&-@B#mrr&Fq{kbGYE zLD}-^D+R^VOhPwzHEme4FqE4%n(wQ$x+BP~|MjNrTwN|Zc+Ph}R zC8>&aA9$Tje^B<^{s9ZgN*gVge9Z8ljk!9D4a_+^k^j0&S`9m`9FFQ8Gt=4mjSp=1 zD$LqHf8G(85SEmQb+Dm%t6_12orXvl4JQ7^S|0nNE`@y02M1wG8kF0WZAS7)7-b|u zw$x~!Um2Ec9hWbzUd@MAXi{&GUdu*%7V;#aVQw^>m9cBKgX3g`1Yp9pX{oU3q9ag=Bd&UbMZS8ey2NC92!h_=6 zy;~1QG5z;$E`cRO`i&2k3@4+*KD69W{F`Rgw#sv}-Okh=+Bq@Ad#Y8fc;hciwJ+?V ze_uELm7A?Bz73oolXw$Up(EHNQD zn^Fh7R;W0;_mSMawFT}hOu^wG+V#xrQ= zsB_P?Q~H=BQOuH&-XX(`h%wqMS-`TCBq1#)A`K}AzXY@2c77Ae%0l;YLgFFMeM$lz6jwIi+$zvsd^du8!3G4h_X7N_$Sj8k%$#^vW z7dd{SaN<~+BH4H%k(GZ$U6M&CjcDY#sA-p7rR+P9MC|ZoCeXMyo>?eU3W6od4#K({ zmK%o>rhnrD;jpj)36(O=OB>NCf42~!U-(b@)@(I8=e{uJr)$49|d#xZ? zgTrh10z0PZ=pM2jxUR)IXI{)lvK#2y-3KfX1X}wkKlt|VN~AS*^?jJ{wQj<-drFzy zL^ss$qTJKjBeeSY@#{D*fh_LX%%z#8tX%4$!`j(GXa~`Turr=V|r+f9J{Lhx}jn@+{>4dF}QmSaTwO=`&LtmpUqYn-!?b zo_^4v3_pkzgHq>-KD4MjfpwXFt-`#wM4qu^)Y2SP!Ph+Be}$R&s^}7CGVQ+EJ=#A# z-rd{33E+m<)b$ol_fLL2*xNtf-P?Qn?#9jqhw zKRg#zM#RNusr>+ZUVln;4x}z(@~U zJz;N3ngocN2+?w62n9_m5z4Tz_4}rly&*!Cv0f{!e`<|V=TNUu|5j^==ZNOAZfV@m zhYwDkR&{>tI=2x+7Uwi;hZ_)fB|XFQ=g?Bn*1LlJleX94@6hvdy?%F1IKT9M?dp^3 zpPobOEGrzm$j3{)Tpb+(IXgRCKDxG?T#=JW+D`aYdwm@`P`>h7yc7gE`8irp zV}*0sf5i&eKHvbHx9jWO5zV!WKXzrO*YkMT)T*RV9i`<@kN-eQFlHwkHm-Phds6wN zUTX!;Ag0J`{=U=eflxiscL8!pP!0*&=GII&>_ZKwY4R#`3?Du~W%4B_JtD_iZoG69!tP%ypkP#il^)e?{w-kK+o-xM6V}JgUqGGp|~`{WmHl zN?Em*exV6o$(A&<@`VLv4Z94O!R*|cZuR{UpNm>*=KC9YAXiGoyUITiGiB#NP&0^% zUL7H+Y9L_@GbO~D@31LL27F3Y5e-NS4rK^V>wui|S}U&m6c7PdG>+xJ#5c9nmtYmU ze*s?ueA~6qY-Z48tMf@n!rHVLS=Po#wTJrBan!NsSZu9x*NFP!k@FiLXg0eNmvt@CuE%T_+IC|>v;(qE!dSk_b1Svn zxh#A3DW|kzqIWO6vuyL`Y;&#I#ZJj;e^t-d%3;@fJx+=^XK75`$njTH(Ez$;tNStI z0qGpCwUdIo-Y~vy&e4noO|@8PEEtTIJmqRG^zEHzo>GdB>Td2>l`$9^6fBNP6dRqq z!i~wOl2ipO|M+X3HFI0o$Gc2z3p6a^cxLF{Dcv#Z2VJSvaK7nRjurC`PI{(^e`uG{ z+*};4!*}}F27PmX?N!ex5j?rG=4xf-L96u-K4=`Pdiq%4m^!~Qom;y5 zojyx{=NzVah6au8Uw&QhqEm>de;lmKfi%xcbI-af9@`!0juxX2t#`>~!mpC1MxC#> zUL^mzs8XYCr1U!-)GNYb8nuu`l~y_WZA0@b_F+tfYPbUUAUV$XNX{7Y!t%r2ah`wO zIoHGeT$7Gw$e(1^rEr-x&%48^ecr50eEN=Wr#4tJ47!~aNzNwTeq#Bvf2w_Z=4@n; z?`-o*jhrK$fRGK3?C~BGA0rR;NTpE&B z>k&u}h%or2d1>ASq7|Ihf3F~Nn%Z(APJ77Am4eq)E;GmS77kuo)wy=Iu(^r~K^+=K z>&_`>kxB=U^4M@?QP5KI!-9e_z1|KyZf*B^ zPdmM>PH+2c>q)=&xZiv7U%hAjUhltKMxQ88ndc;Qj(zrosm`-|?m5#mUA_BsZaHwGrxO2h`{_<^>%so#KAtr=qattezV&i6lQb1& zN@gcSLp>dHE2kpwVEBtplg`%3fQ-vDadOzUD(Q}|tq#79ma%r#c)d$Tll0gmCG5l2 zDk+djn)cCmQvbf@5Y6mp_?0W(>iuoCu^V923=@>@(0bcef4R2T3MlWSzKVj<*RE3K zSM)0Ig~Lt>{TLG!f7|<_0_FajKDXlk-WB5fHkR@KttVCc|0hqkAL2jV%i}V6%n3<~ ze9SZUPh@o6%Ws9`6PELz@R&C6{JKw8mCKim<A4gB?^;PTw6TQ$_ntl8s__43 zTTh=n@c;XGZovPwL+&lNfqU5nI*x_VZFd76(-S(Be^m`jT!68_7-iyyIS)TNp@g;= zRTX>_IqYbo*`~$9lAU(R{NH;0H(1+M+gP^#AMe!G|Fhoq!}`CEr(#dsNP`V${8bM2 z_bKu<*k0apy?^eBxu@s0>wgt5z{Tr->+#OB>iU2D^x4Dt?_Qp@xiJ)nT!jtNz8yS6 z6_4Ebf7guloGpuqgq7W*E>62*49d5;7GxF=OeqaF)a{9}hD<1pf;BWbDp;(dw55f> zs4O<1hl>28QqEXkcMMae}(o;p2>=ox5P@HoXRbCU3J!uu$d7 z9_BF`JJoD#y!D%Wl!4_O>kcCZjUiNbMPS$R;fBqh}Neb2gi7(UFK$00@87)!wbaej>_P zif$4xWeU}Z&zA=$&%bI%EJIu4R<#NLs(x}Au?#w?Tkhq-Nwp^-5eTX_LSR+9e?E=r zxbOw2tlKI zS#7Q%3ri~T197VT)i-E>ws9km*6ZMmEl0oBaj^d94EvU61+7*G?Hn!6vqF}6GQU8o z#?xHJy2Hw0ebq+YX{Ug{eEoChf9px->*(z3*Zr@L`d^>^*LrnsJ_hrDRp$oEHRDGV zy~$>Y~Rdha@he7SrQI$N6J=`)$umpsn zht61GLq2bq?P&v7;D#dq4Ua%KDsw%r0WF4W-yz+=zn`?F1Jgmde`$S+?JnUt_G;nu zZ-DNMN&q>>Hu~Co!(5v6N(mB{r%M%TTs>v{aOxn*#*25#-5E(*n2Um)tH;=p%Com0 zf9a-*iE7&}Q8zTa=(Jf*ojTSZp+Hi%xT>D+0VcBKF&^|bt6wVt(9O%=3o5FnSQ9$I zfY`04kB^v7NMTy^f1$P2>rLGEF`KZtK(@BOZHBUqP^?D7i$Cj!>2(I(5d9Y)t(Y(& zue%}Z@{}fG%!YZFXQR!KZC2`5&C+TL3>)vek-kri2;7*>r#4&vc>Ch~&907u?h}Kf zXlc!@hjGlRm0x9i;-eimD1A>^d#^~tD7m^xC16b1=<(Z^e~q2JeS3j}!MCZ-*VL@D zxznXoj_mO8{NT;m{>hKKhv%pJdvD*oJZ*rwU9Wny`~RK)w0m%Nes*xQzo6<)uUD@u z3m=>vynS{>k~?+c#&s2XFRI zRwBLsQGDlfe_y$kDw{(t+))1Auw-`4i_gZ=-#JbKcVuqIcMiuPcm z;$aR)9c8#41KeH`EDIJM0O&x?)lkj4@EH-8t8}6ofOc^Ys-+QxxwT%QYVD@m0BACM zxhA|O5Ei_(s=h8zThRmM8tB~wqFWyUS3G~N1G7>!e{hn|RHps2=A!xZnPaNCnS3d) zCU4axtz|NuJqP(gYHt8uq%`c-Z!fQvSPEgT0nf)YgOm%w1~I*pbDvG}R1-F8X`bff zd8#LRYRL5DrFv@0_2eddYRL9v(mj1{mhU}__Wuc?BG35kl7HVM|M%9jr&|y9fA{h{ zm%;$;AsEtyEA`{wRU>KVrg`8yl2RBjM58BB#EM^)6k3U8}vfcIT#1HB#i`zAkN2e=g*)K(~Xl!&3-pCTSrgxyjT# zIXhGZ7s5h$#t^|%Q;R4!dy=$BsylbK`qq)W8HOX!F{MEtXepxpp z=R+1#qZc-^g&eHdl+0=~B;7F6M7MUcM)yz7s?E|YU)nHI`)wQlVgKh!+n02q z!+X2()S4jCdPH`hc^%C+ILztZ7i1mx1aR3tM=bJf4#?#ALRf0coxe4dF}Q;Sc7AdkqMO| zlGd4(8H6Lp|4Ci;yLS)va#G95s*00;T}eBApi)rlGF47(kRs3gY{I3cY*l!UDzAjg zl05M9lzSPzmDtNOb+b^-A?evM^i8x}FJ*>FnDPDonY|lbDdWF;ZcqiRxUtnU-ZglN zf0>_lLrHo{+gJwo(YgH-;$(jJCg@H1#g?uhudGv>2d9$0zW0;N>uB*N%7LvjFE!BJ zWXr!vuKbEuDKzGdGA>ahn(Z0Tr*1_Vr5A^qf#N`L$QRd*sqpH&~q_pw* z_19BSMZ^cYGV1-&nb;~#O|51ewcFE3a`YxQkgW12vo&1B_>e~rKz z#Hfq8h6KM_w{um?GDRCvI)H>p*DIy~`C$u{)y3dxT zMKS1P-lEVK9+P^dGnX!_qmr_`f2?QeMx9e~uFU)>>hb85#x5`kLnu2@3`lZJB>5|H zWQo32u*LzJWkbv^DNII_d_j)LU%qSqFXZCSpBor;C@RLsE4X^rQr;Zr_iH+M?%X7}tjce3QDX; zvJuVCbz{2ero9k*@o$yFyON)~^t7-CdNkz1QI1(Sb}p##MlezvLq!a+X}-T{Nb~)T z)7oA+E|O(rCS@#K$M!;aOazL-8@i1Ron3DOs>xK>;GLj20o?lRj|od;-@mg-`qpt! zf()rPQX+bwFuib6O8(aO6>*;WB>3Zv^nGq%QoG)FPYG z9dIt%OPD4rsxIR#**i$?J)d*0!=L}Wa$(KtZc@j=N>f(FKmQqS2F%HxD|@75Z0w+$ z#8+n)H~kLHK$?=d-mo9d*@Jb%3v&neq2*DbdB7%U!3sf1raefDRfo1TZJN^jc|!+I$8|z^bGuwH%nid`>21 ze>5|i`xN6*OLO|}CTv^De-5_o_r=)OeYB--fbmAGH#Og!s{c;V+&GNuqZ)LE>Ns}R zc-x-pHUXmuT=z;#^Y!}DUjY3kBE>x|u>~7opq*3x_L9<#iF=t)^Z7}3F_m413RfBHz0%Zo`pb&Km9lB>JHafpg|;zU zZf8Z3vxyg|-3TwL^6e!Tqm22^wytF-qabOu*5Oaj%c!+Yqc$TL5*E`4NDk?k2nxS6 zucN;-w7Q>VJnMdk^?%B?3l&6m&iefew0;GV)AR-n$!gbWrw`X|LeGb7r7S4FA634# z5)LN#ZUckum+e>zKfPpWN+SzG+nm-r`5mUz(E4+sYW1Pzc3!?!(WYxy(c5(+a{A@u zm*lHGJSzc+Z!ZGe39as%y04H}`>i`6$2*%IU8#8kZ>d#3yMO(5vl1$wFG3|U+dtiS z)YssZzUMfULZ?4uyal-Bp1b-!WL(-8pWU15bUix;XW%Y2+^aKxSRqeDF`>0voX??( zSq%*<0B8v8TOwwm9l~AA{g;W3Wh7E&WxMl*u)6TM`a-DJS!tA@go*nbk>aPXD2)ar zY$AX~#kr(>Xy)IddvLsq*fAv$%_<4)bWCubd#K_vUZzg@dswJbrsx zW;Ix141cKO4%o=_#&9j=nb``oiPq;fNqu3+t*(1$uV_E!9`OUZge!BH| z=fVE_KAwsZ-U+a%LWyD^+Tic{A9OU1KGQ^d>V}BSFLivlue8fV1=8-T9o8IZ0j1Ipk zZQU?1R!m6(f~G{fu<)U*8qzEgDy6VR){n;O-MQoo)}Z5X;69w8C6o<{P`do7lJ2J2 zw|1+}UaF&YCBvA7xk^b>Xp{VoZwRDz+Q2{FWYnX1`CfE!e8tv)p9K*d>98%~_kw!3 zWOvI<=eTYS!@yN)o#ROiEE&ql(?*1~1nFyVW)`cg8M7e`XQ4Kms4Nb*XAM5umIF4? z{c7mA{rb8QpD4HSjylI-J-a^`tbaEyOb4UY1*A0%u?NO9vW=#QCK+(#N>q;Susx9F$O;uN8gH1K7TX*6|yv5C=%G?_}znF{9@4#x@c;S@# z9vlm%)L>YVj_=&4l;U6Zw12NO6RRP#dPSz2JYUjzQ*1$JKMgy3Ns*VUREh#R82Yeu z@DgKQ#F0F>WOTx(=)Z91y@;?6stbmh9cMfxBPE}v7(dew{EBtT7#1C9z2X^_QhE}# z`tTEBxjFL7Lt&D;j(=PZNVOWlkY(D15y8A5;iUzmvBM@48Zna7cz?D5v2&!wIKQ-P zM3iveX6cwDtv(!5GOc*;nd(<#n$(;ib-xf&T4KQj5p~CDw(RdS5>iNM#<+@)JCZMk zMQqUoZGpb#S|N|$kForzdf$hg-X|h4GeAEctE9DKm2Fdd>*=tvH~<}dUJ49DeG&30 zI%hUG)iWz&-(d@GCV%5n4O7pgB0fda9Si=zYKuzNWRA2gnE%MmXB1ENt&y* zINd6HU*7YWSMHuCT2-i4Kb5<87xN?pRp->m@RnqpD@#Q%r$%75AocdELgAdUi0-Co zJZn|D^G;A5!={5S(DEpt23gJjWrN*VSniI2mt{vNG-S>DHGizZEA(wsqJ`tk*QA`L>E^f>LIHyIe**r6c%$9Bu9qpZ5R>XRX&uP)#;nzV1 z0esR6xmE6eZk7CXh5WC*?d{6_Pw&Y?{h(?fL%$00960W^~H~00Yzk06z9;NdN!< delta 18692 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 diff --git a/helm/sentinel-nodepools/values-oci.yaml b/helm/sentinel-nodepools/values-oci.yaml new file mode 100644 index 0000000..0d482df --- /dev/null +++ b/helm/sentinel-nodepools/values-oci.yaml @@ -0,0 +1,23 @@ +# OCI/OKE overrides for Sentinel nodepools (v0.2.0 config format) + +hyperfleet-sentinel: + config: + resourceType: nodepools + resourceSelector: [] + + hyperfleetApi: + baseUrl: http://hyperfleet-api:8000 + timeout: 5s + + broker: + type: rabbitmq + topic: hyperfleet-nodepools + rabbitmq: + url: amqp://guest:guest@rabbitmq:5672/ + exchangeType: topic + + monitoring: + podMonitoring: + enabled: false + prometheusRule: + enabled: false From 06c0fe01ce1fa8373497bcb50cf703d30989efb4 Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Thu, 16 Apr 2026 09:15:06 -0400 Subject: [PATCH 4/5] Fix precondition guards and use dynamic nodepool spec values Add has() guards to clusterNotReady/nodepoolNotReady precondition expressions so fresh resources without status.conditions fall back to true instead of erroring. Replace hardcoded NodePool spec values in adapter-task-config with index .nodepoolSpec lookups matching the pattern in nodepool-manifest.yaml. --- .../adapter-task-config.yaml | 24 ++++++++++--------- .../adapter-task-config.yaml | 8 ++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/helm/adapter-hypershift-nodepool/adapter-task-config.yaml b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml index b4e86d9..979b45f 100644 --- a/helm/adapter-hypershift-nodepool/adapter-task-config.yaml +++ b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml @@ -65,9 +65,11 @@ preconditions: field: "spec" - name: "nodepoolNotReady" expression: | - status.conditions.filter(c, c.type == "Ready").size() > 0 - ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" - : true + !has(status) || !has(status.conditions) + ? true + : (status.conditions.filter(c, c.type == "Ready").size() > 0 + ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" + : true) # Fetch parent cluster details (need the cluster name for clusterName ref) - name: "clusterDetails" @@ -118,22 +120,22 @@ resources: hyperfleet.io/nodepool-name: "{{ .nodepoolName }}" spec: clusterName: "{{ .clusterName }}" - replicas: 2 + replicas: {{ index .nodepoolSpec "replicas" | default 2 }} management: autoRepair: true upgradeType: Replace platform: type: OCI oci: - instanceShape: "{{ .ociShape }}" + instanceShape: "{{ index .nodepoolSpec "instanceShape" | default .ociShape }}" instanceShapeConfig: - ocpus: 4 - memoryInGBs: 16 - availabilityDomain: "{{ .ociAD }}" - subnetId: "{{ .ociSubnetId }}" - bootVolumeSize: 120 + ocpus: {{ index .nodepoolSpec "ocpus" | default .ociOcpus }} + memoryInGBs: {{ index .nodepoolSpec "memoryInGBs" | default .ociMemoryGBs }} + availabilityDomain: "{{ index .nodepoolSpec "availabilityDomain" | default .ociAD }}" + subnetId: "{{ index .nodepoolSpec "subnetId" | default .ociSubnetId }}" + bootVolumeSize: {{ index .nodepoolSpec "bootVolumeSize" | default .ociBootVolumeGB }} release: - image: "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" + image: "{{ index .nodepoolSpec "releaseImage" | default "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" }}" discovery: namespace: "{{ .namespace }}" by_selectors: diff --git a/helm/adapter-hypershift/adapter-task-config.yaml b/helm/adapter-hypershift/adapter-task-config.yaml index 857584f..df9ba0a 100644 --- a/helm/adapter-hypershift/adapter-task-config.yaml +++ b/helm/adapter-hypershift/adapter-task-config.yaml @@ -53,9 +53,11 @@ preconditions: field: "generation" - name: "clusterNotReady" expression: | - status.conditions.filter(c, c.type == "Ready").size() > 0 - ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" - : true + !has(status) || !has(status.conditions) + ? true + : (status.conditions.filter(c, c.type == "Ready").size() > 0 + ? status.conditions.filter(c, c.type == "Ready")[0].status != "True" + : true) - name: "validationCheck" expression: | From 30b51ef3a215738d639d10a21abfd74d582783ee Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Thu, 16 Apr 2026 12:06:20 -0400 Subject: [PATCH 5/5] Guard clusterAvailable conditions and fix YAML quoting in nodepool adapter Add has() guard to clusterAvailable expression so missing conditions field returns false instead of erroring. Switch outer double quotes to single quotes on nodepoolSpec index expressions to avoid nested quote YAML parsing errors. --- .../adapter-task-config.yaml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/helm/adapter-hypershift-nodepool/adapter-task-config.yaml b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml index 979b45f..a67812c 100644 --- a/helm/adapter-hypershift-nodepool/adapter-task-config.yaml +++ b/helm/adapter-hypershift-nodepool/adapter-task-config.yaml @@ -94,7 +94,13 @@ preconditions: capture: - name: "clusterAvailable" expression: | - items.filter(s, s.adapter == "adapter-hypershift").size() > 0 ? (items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available").size() > 0 ? items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available")[0].status == "True" : false) : false + items.filter(s, s.adapter == "adapter-hypershift").size() > 0 + ? (has(items.filter(s, s.adapter == "adapter-hypershift")[0].conditions) + ? (items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available").size() > 0 + ? items.filter(s, s.adapter == "adapter-hypershift")[0].conditions.filter(c, c.type == "Available")[0].status == "True" + : false) + : false) + : false - name: "validationCheck" # Only proceed if nodepool is NOT Ready AND HostedCluster adapter reports Available @@ -127,15 +133,15 @@ resources: platform: type: OCI oci: - instanceShape: "{{ index .nodepoolSpec "instanceShape" | default .ociShape }}" + instanceShape: '{{ index .nodepoolSpec "instanceShape" | default .ociShape }}' instanceShapeConfig: ocpus: {{ index .nodepoolSpec "ocpus" | default .ociOcpus }} memoryInGBs: {{ index .nodepoolSpec "memoryInGBs" | default .ociMemoryGBs }} - availabilityDomain: "{{ index .nodepoolSpec "availabilityDomain" | default .ociAD }}" - subnetId: "{{ index .nodepoolSpec "subnetId" | default .ociSubnetId }}" + availabilityDomain: '{{ index .nodepoolSpec "availabilityDomain" | default .ociAD }}' + subnetId: '{{ index .nodepoolSpec "subnetId" | default .ociSubnetId }}' bootVolumeSize: {{ index .nodepoolSpec "bootVolumeSize" | default .ociBootVolumeGB }} release: - image: "{{ index .nodepoolSpec "releaseImage" | default "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" }}" + image: '{{ index .nodepoolSpec "releaseImage" | default "quay.io/openshift-release-dev/ocp-release:4.20.2-x86_64" }}' discovery: namespace: "{{ .namespace }}" by_selectors: