Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
35e794e
tilt implementation
ddelpiano Apr 16, 2026
b371161
generate tilt configuration
ddelpiano Apr 16, 2026
fb26db8
mongo and kc upgrade
ddelpiano Apr 16, 2026
50d4db0
dns resolution issue
ddelpiano Apr 16, 2026
85ecd94
minikube local development cluster ip logic updated
ddelpiano Apr 16, 2026
c003f50
finalizing last changes after CH rebase from develop
ddelpiano Apr 16, 2026
ac07aa2
requirement missing
ddelpiano Apr 16, 2026
462e999
fixing operator none in database config
ddelpiano Apr 16, 2026
0ee7681
another operator instance
ddelpiano Apr 16, 2026
dbb05a9
removing immutable matchLabel, only app should be in matchLabel where…
ddelpiano Apr 16, 2026
78f250e
resource policy set to keep
ddelpiano Apr 17, 2026
5bb5adc
removing resource policy
ddelpiano Apr 17, 2026
03c447b
fix mount volume rights and pvc existing data that kafka does not like
ddelpiano Apr 17, 2026
cffa4a3
linter fix
ddelpiano Apr 28, 2026
fe2a481
Potential fix for pull request finding 'Unused import'
ddelpiano Apr 28, 2026
4037a96
Potential fix for pull request finding 'Unused local variable'
ddelpiano Apr 30, 2026
6882671
chore: make get_cluster_ip a bit more robust and independent on minik…
zoran-sinnema May 4, 2026
9ad54b0
chore: tiltfile setup infra waits for helm and jobs to be finished be…
zoran-sinnema May 4, 2026
0ba9fbe
chore: removed wait for webhook, tiltfile now contains helm ... --wai…
zoran-sinnema May 4, 2026
383bbc1
chore: remove hardcoded storageClassName:standard from kafka (events)…
zoran-sinnema May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions applications/accounts/deploy/templates/_components.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"subComponents": {},
"config": {
"kc.user.profile.config": [
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}"
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"stripe_uid\",\"displayName\":\"Stripe UID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}"
]
}
}
Expand Down Expand Up @@ -72,4 +72,4 @@
{{template "deploy_accounts_utils.user_profile_provider_component" }},
{{template "deploy_accounts_utils.key_provider_component" }}
},
{{- end -}}
{{- end -}}
16 changes: 11 additions & 5 deletions applications/events/deploy/templates/deployments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ spec:
- name: KAFKA_CONTROLLER_QUORUM_VOTERS
value: 1@kafka-0.broker.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.apps.events.kafka.controllerPort }}
- name: KAFKA_LOG_DIRS
value: /var/lib/kafka/data
value: /var/lib/kafka/data/logs
- name: KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS
value: "0"
{{- range $key, $value := .Values.apps.events.kafka.config }}
Expand All @@ -62,11 +62,15 @@ spec:
periodSeconds: 15
timeoutSeconds: 5
livenessProbe:
tcpSocket:
port: client
initialDelaySeconds: 30
exec:
command:
- /bin/sh
- -c
- timeout 10 /opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
timeoutSeconds: 15
failureThreshold: 3
lifecycle:
preStop:
exec:
Expand All @@ -79,6 +83,8 @@ spec:
volumeMounts:
- mountPath: /var/lib/kafka/data
name: data
securityContext:
fsGroup: 1000
terminationGracePeriodSeconds: 30
updateStrategy:
type: RollingUpdate
Expand Down
1 change: 0 additions & 1 deletion applications/events/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ kafka:
imagePullPolicy: IfNotPresent
clusterId: 5L6g3nShT-eMCtK--X86sw
storage: 10Gi
storageClassName: standard
config:
auto.create.topics.enable: "true"
num.partitions: "3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
livenessProbe:
exec:
command:
- mongo
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
Expand All @@ -21,10 +21,10 @@
readinessProbe:
exec:
command:
- mongo
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 5
timeoutSeconds: 5
failureThreshold: 6
{{- end }}
{{- end }}
2 changes: 1 addition & 1 deletion deployment-configuration/helm/templates/auto-database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spec:
---
{{- end }}
{{- define "deploy_utils.database" }}
{{- if and (eq .app.harness.database.type "postgres") .app.harness.database.postgres.operator }}
{{- if and (eq .app.harness.database.type "postgres") (dig "postgres" "operator" false .app.harness.database) }}
{{- include "deploy_utils.database.postgres.operator" . }}
{{- else }}
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ spec:
selector:
matchLabels:
app: {{ .app.harness.deployment.name| quote }}
{{- include "deploy_utils.labels" .root | indent 6 }}
template:
metadata:
{{- if .app.harvest }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ spec:
protocol: UDP
- port: 53
protocol: TCP
{{- if and (eq .app.harness.database.type "postgres") .app.harness.database.postgres.operator }}
{{- if and (eq .app.harness.database.type "postgres") (dig "postgres" "operator" false .app.harness.database) }}
# Allow CNPG pods to reach the Kubernetes API server
{{- $apiCidrs := list }}
{{- $kubeSvc := (lookup "v1" "Service" "default" "kubernetes") }}
Expand Down
3 changes: 1 addition & 2 deletions deployment-configuration/helm/templates/auto-volumes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ spec:
accessModes:
{{- if or (not (hasKey .app.harness.deployment.volume "usenfs")) (not .app.harness.deployment.volume.usenfs) }}
- ReadWriteOnce
storageClassName: standard
{{- else }}
- ReadWriteMany
storageClassName: {{ printf "%s-%s" .root.Values.namespace .root.Values.apps.nfsserver.storageClass.name }}
Expand All @@ -27,4 +26,4 @@ spec:
---
{{- include "deploy_utils.pvolume" (dict "root" $ "app" $app) }}
{{- end }}
{{- end }}
{{- end }}
67 changes: 67 additions & 0 deletions deployment-configuration/tilt-deploy.ext
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
load('ext://namespace', 'namespace_create', "namespace_inject")


def deploy(name, namespace, extra_env, watch):

# create namespaces
namespace_create(namespace)

# load helm chart
yaml = decode_yaml_stream(helm(
"deployment/helm",
# The release name, equivalent to helm --name
name=name,
# The namespace to install in, equivalent to helm --namespace
namespace=namespace,
# The values file to substitute into the chart.
values=["deployment/helm/values.yaml"],
# Values to set from the command-line
set=["service.port=1234", "ingress.enabled=true"]
)
)

source_root = os.path.abspath(os.getcwd())
# modify deployments
for r in yaml:
if r.get("kind") == "Deployment":
deployment_name = r["metadata"]["name"]
print("+ patching deployment:", deployment_name)
r["spec"]["template"]["spec"].setdefault("volumes", []).append({
"name": name + "-root",
"hostPath": {
"path": source_root
}
})
for container in r["spec"]["template"]["spec"]["containers"]:
print(" + modifying container:", container["name"])
print(" - add " + name + " root folder")
container.setdefault("volumeMounts", []).append({
"mountPath": "/usr/src/" + name,
"name": name + "-root"
})
if "resources" in container:
print(" - modifying resource requests and limits")
if "limits" not in container["resources"]:
container["resources"]["limits"] = {}
if "requests" not in container["resources"]:
container["resources"]["requests"] = {}
container["resources"]["requests"]["cpu"] = "100m"
container["resources"]["requests"]["memory"] = "256Mi"
container["resources"]["limits"]["cpu"] = "8000m"
container["resources"]["limits"]["memory"] = "4096Mi"

if deployment_name in extra_env and len(extra_env[deployment_name]) > 0:
print("Adding tasks images dependencies to env ", deployment_name)
for env in extra_env[deployment_name]:
container["env"].append({
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

container["env"] may not exist on all rendered Deployments. This will raise a KeyError when adding task-image env vars. Use container.setdefault("env", []).append(...) (or initialize env if missing) before appending.

Suggested change
container["env"].append({
container.setdefault("env", []).append({

Copilot uses AI. Check for mistakes.
"name": env, "value": env
})

if not watch:
# don't watch mnp folder
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "mnp" should be "npm".

Suggested change
# don't watch mnp folder
# don't watch npm folder

Copilot uses AI. Check for mistakes.
watch_settings(ignore=source_root)
else:
print("Watching for file changes")

# install applications
k8s_yaml(namespace_inject(encode_yaml_stream(yaml), namespace))
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,30 @@
class cloudharness_djangoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'cloudharness_django'

def ready(self):
# imports
import sys
for skip_cmd in [
"--help",
"collectstatic",
"compilemessages",
"compress",
"dbshell",
"dumpdata",
"loaddata",
"makemessages",
"makemigrations",
"migrate",
"reset_db",
"showmigrations",
"sqlmigrate",
"squashmigrations",
"test",
]:
# for these commands we skip initializing the event listener
if skip_cmd in sys.argv:
return

from cloudharness_django.services.events import init_listener_in_background
init_listener_in_background()
Comment on lines +8 to +33
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting the Kafka listener in AppConfig.ready() can run multiple times in common Django setups (e.g. runserver autoreloader spawns a second process; multi-worker gunicorn/uWSGI will start one per worker). This can lead to duplicate consumers/extra connections or repeated retries. Add guards (e.g. check os.environ.get("RUN_MAIN") for runserver, or require an explicit env flag) to ensure it starts exactly once per intended process.

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import time

from cloudharness.applications import ConfigurationCallException

from django.conf import settings
Expand Down Expand Up @@ -26,26 +28,31 @@ def event_handler(app, event_client, message):
log.info(f"{event_client} {message}")
if resource in ["CLIENT_ROLE_MAPPING", "GROUP", "USER", "GROUP_MEMBERSHIP", "ORGANIZATION_MEMBERSHIP"]:
try:
time.sleep(1) # wait a bit to make sure the transaction is committed in Keycloak before trying to fetch the updated data
init_services()
user_service = get_user_service()
Comment on lines 29 to 33
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time.sleep(1) is executed for every handled Keycloak event, adding a fixed 1s latency and reducing throughput of the consumer thread. Consider replacing this with a bounded retry/backoff when the subsequent get_user/get_group fetch fails due to eventual consistency, so the common case doesn’t always pay the delay.

Copilot uses AI. Check for mistakes.
auth_client = get_auth_service().get_auth_client()

if resource == "GROUP":
kc_group = auth_client.get_group(resource_path[1])
user_service.sync_kc_group(kc_group)
return
if resource == "USER":
kc_user = auth_client.get_user(resource_path[1])
user_service.sync_kc_user(kc_user, delete=operation == "DELETE")
return
if resource == "CLIENT_ROLE_MAPPING":
# adding/deleting user client roles
# set/user user is_superuser
kc_user = auth_client.get_user(resource_path[1])
user_service.sync_kc_user(kc_user)
return
if resource == "GROUP_MEMBERSHIP" or resource == "ORGANIZATION_MEMBERSHIP":
# adding / deleting users from groups, update the user
# updating the user will also update the user groups
kc_user = auth_client.get_user(resource_path[1])
user_service.sync_kc_user(kc_user)
return
except Exception as e:
log.error(e)
raise e
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Python, raise e resets the traceback context. Use a bare raise to preserve the original stack trace (and consider log.exception(...) if you want the traceback in logs).

Suggested change
raise e
raise

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -91,26 +98,24 @@ def init_listener():
if not hasattr(settings, "PROJECT_NAME"):
raise KeycloakOIDCNoProjectError("Project name not found, please set PROJECT_NAME in your settings module")

kafka_group_id = settings.PROJECT_NAME.lower()
global _message_service_singleton
if _message_service_singleton is None:
_message_service_singleton = KeycloakMessageService(settings.PROJECT_NAME)

_message_service_singleton = KeycloakMessageService(kafka_group_id)
_message_service_singleton.setup_event_service()


def init_listener_in_background():
import threading
import time
from cloudharness import log

def background_operation():
listener_initialized = False

while not listener_initialized:
while True:
try:
init_listener()
log.info('User sync events listener started')
listener_initialized = True
break
except:
log.exception('Error initializing event queue. Retrying in 5 seconds...')
time.sleep(5)
Comment on lines +114 to 121
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This retry loop runs indefinitely on any exception. Since it is executed in a background thread (started below), make sure failures don’t prevent clean shutdown and aren’t retried forever unintentionally. Consider (a) catching Exception instead of bare except:, (b) adding a max retry count / circuit breaker, and (c) starting the thread as a daemon so the process can exit if initialization never succeeds.

Copilot uses AI. Check for mistakes.
Expand Down
4 changes: 4 additions & 0 deletions libraries/cloudharness-common/cloudharness/auth/keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ def user_add_update_attribute(self, user_id, attribute_name, attribute_value):
{
'attributes': attributes,
'username': user.username,
'firstName': user.first_name,
'lastName': user.last_name,
'email': user.email,
}
)
Expand All @@ -659,6 +661,8 @@ def user_delete_attribute(self, user_id, attribute_name):
{
'attributes': attributes,
'username': user.username,
'firstName': user.first_name,
'lastName': user.last_name,
'email': user.email,
})
return True
Expand Down
7 changes: 5 additions & 2 deletions libraries/cloudharness-common/cloudharness/sentry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ def get_dsn(appname):
dsn = get_dsn('notifications')
"""
url = get_common_service_cluster_address() + f'/api/sentry/getdsn/{appname}'
response = requests.get(url, verify=False).json()
dsn = response['dsn']
try:
response = requests.get(url, verify=False, timeout=5).json()
dsn = response.get('dsn')
except Exception:
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should at least log this error

if dsn and len(dsn) > 0:
return dsn
else:
Expand Down
2 changes: 1 addition & 1 deletion libraries/cloudharness-common/cloudharness/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def get_cloudharness_events_client_id():


def get_cloudharness_events_service():
return get_service_cluster_address('BOOTSTRAP')
return get_service_cluster_address('bootstrap')


def get_service_cluster_address(cloudharness_app_name):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,8 @@ def hosts_info(values):
f"127.0.0.1\t{' '.join('%s.%s' % (values[KEY_APPS][s][KEY_HARNESS][KEY_SERVICE]['name'], values['namespace']) for s in deployments)}")

try:
ip = get_cluster_ip()
local = values.get('local', False)
ip = get_cluster_ip(local=local)
except:
logging.warning('Cannot get cluster ip')
ip = "127.0.0.1"
Expand Down
2 changes: 1 addition & 1 deletion tools/deployment-cli-tools/ch_cli_tools/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def __finish_helm_values(self, values, defer_task_images=False):
values['local'] = self.local
if self.local:
try:
values['localIp'] = get_cluster_ip()
values['localIp'] = get_cluster_ip(local=True)
except subprocess.TimeoutExpired:
logging.warning("Minikube not available")
except:
Comment on lines 215 to 219
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_cluster_ip(local=True) now swallows timeouts/errors internally and falls back to get_host_address(), so this except subprocess.TimeoutExpired branch is effectively dead code. Either let TimeoutExpired propagate from get_cluster_ip (so callers can warn appropriately) or simplify this try/except to match the new behavior.

Copilot uses AI. Check for mistakes.
Expand Down
Loading
Loading