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
53f39ba
tilt implementation
ddelpiano Apr 16, 2026
5543c34
generate tilt configuration
ddelpiano Apr 16, 2026
d15822d
mongo and kc upgrade
ddelpiano Apr 16, 2026
69e51c7
minikube local development cluster ip logic updated
ddelpiano Apr 16, 2026
7e8f56c
finalizing last changes after CH rebase from develop
ddelpiano Apr 16, 2026
5f27146
requirement missing
ddelpiano Apr 16, 2026
e7eda9e
fixing operator none in database config
ddelpiano Apr 16, 2026
1127b87
another operator instance
ddelpiano Apr 16, 2026
c3be506
removing immutable matchLabel, only app should be in matchLabel where…
ddelpiano Apr 16, 2026
985ca17
resource policy set to keep
ddelpiano Apr 17, 2026
dc5ab5c
removing resource policy
ddelpiano Apr 17, 2026
b5f1a45
fix mount volume rights and pvc existing data that kafka does not like
ddelpiano Apr 17, 2026
0828852
linter fix
ddelpiano Apr 28, 2026
3dd1fe3
Potential fix for pull request finding 'Unused import'
ddelpiano Apr 28, 2026
351f258
Potential fix for pull request finding 'Unused local variable'
ddelpiano Apr 30, 2026
9b20eac
chore: make get_cluster_ip a bit more robust and independent on minik…
zoran-sinnema May 4, 2026
f0383b9
chore: tiltfile setup infra waits for helm and jobs to be finished be…
zoran-sinnema May 4, 2026
f88d433
chore: removed wait for webhook, tiltfile now contains helm ... --wai…
zoran-sinnema May 4, 2026
98d7314
chore: remove hardcoded storageClassName:standard from kafka (events)…
zoran-sinnema May 5, 2026
35d2675
Linting fix
filippomc May 25, 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 -}}
2 changes: 2 additions & 0 deletions applications/events/deploy/templates/deployments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ spec:
volumeMounts:
- mountPath: /var/lib/kafka
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 @@ -27,28 +29,33 @@ 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 30 to 34
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])
# invalidate the user cache to force the user to be reloaded
invalidate_user_cache(kc_user.id)
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 @@ -94,26 +101,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 +117 to 124
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 @@ -650,6 +650,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 @@ -673,6 +675,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
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,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 @@ -212,7 +212,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 214 to 218
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
load('{{ch_root}}/deployment-configuration/tilt-deploy.ext', 'deploy')
load('ext://uibutton', 'cmd_button')

config.define_bool('setup-infrastructure')
config.define_bool('watch')
cfg = config.parse()
setup_infrastructure = cfg.get('setup-infrastructure', False)
watch = cfg.get('watch', False)
if setup_infrastructure:
# setup ingress
print("Installing ingress controller")
# local("cd infrastructure/cluster-configuration && source cluster-init.sh")
local("kubectl get namespace ingress-nginx 2>/dev/null 1>/dev/null || bash -c 'helm upgrade --install ingress-nginx ingress-nginx --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace --version v4.2.5 --wait --wait-for-jobs'")
# print("Let's wait a few seconds...")
# local("sleep 30")
else:
print("To setup the infrastructure (f.e. ingress controller)")
print("run: tilt up -- --setup-infrastructure")

if not watch:
print("To watch file changes, run: tilt up -- --watch")


# build images
{% for image in images -%}
docker_build(ref='{{image.image}}', context='{{image.context}}', dockerfile='{{image.docker.dockerfile}}', build_args={{image.docker.buildArgs}}{% if image.is_task %}, match_in_env_vars=True{% endif %})
{% endfor %}

extra_env = {}
{% for image in images -%}
{% if image.is_app -%}
extra_env.setdefault("{{ image.name }}", [])
{% for task in images -%}
{% if task.is_task and task.parent_app_name == image.name -%}
extra_env["{{ image.name }}"].append("{{ task.image }}")
{% endif -%}
{% endfor -%}
{% endif -%}
{% endfor %}

# deploy
deploy(name='{{name}}', namespace='{{namespace}}', extra_env=extra_env, watch=watch)

{% for app in apps -%}
# Add Tilt ui elements for: {{app.name}}
k8s_resource(
'{{app.app_key}}',
links=[link('http://{{app.name}}.{{domain}}', 'Open {{app.name}} page')]
)
cmd_button('{{app.app_key}}:set debug mode',
argv=["sh", "-c", "kubectl -n {{namespace}} patch deployment {{app.app_key}} --patch '{\"spec\": {\"template\": {\"spec\": {\"containers\": [{\"name\": \"{{app.app_key}}\", \"command\": [\"/bin/bash\"], \"args\": [\"-c\", \"sleep infinity\"], \"livenessProbe\": null, \"readinessProbe\": null}]}}}}'"],
resource='{{app.app_key}}',
icon_name='bug_report',
text='set debug mode',
)
{% endfor %}
Loading
Loading