Skip to content

values.yaml

global:
  image:
    repository: registry.datasapience.ru/klmg/predicate/predicate # Change to your repository
    tag: 3.2.0 # Change to your version


service:
  annotations: # from update pod with new image
    rollme: "{{ randAlphaNum 5 | quote }}"
    dependency: predicate-postgres, predicate-celery-worker, predicate-celery-manager

  name: predicate

  extraLabels:
    application: kolmogorov
    component: predicate
    name: predicate

  replicas: 1

  image:
    repository: "{{ .Values.global.image.repository }}"
    tag: "{{ .Values.global.image.tag }}"

  ingress:
    uriPrefix: /predicate/api(/|$)(.*)
    pathType: ImplementationSpecific
    host: "kolmogorov" # Change to your host
    baseDomain: "k8s.datasapience.ru" # Change to your base domain
    ingressClassName: nginx
    annotations:
      nginx.ingress.kubernetes.io/rewrite-target: /$2
    tls:
      secretName: dev-wildcard

  service:
    port: 8000

  command:
    - /bin/sh
    - -c
  args:
    - |
      gunicorn predicate.services.api.app:app \
      --workers=3 \
      --worker-class=uvicorn.workers.UvicornWorker \
      --bind=0.0.0.0:8000 \
      --timeout=600 \
      --reuse-port

  resources:
    limits:
      cpu: 2000m
      memory: 2000Mi
    requests:
      cpu: 20m
      memory: 448Mi

  secrets:
    - name: predicate-secret
      data:
        - name: PREDICATE_ROOT_PATH
          value: "/predicate/api"
        - name: PREDICATE_DB_URL
          value: "postgresql+psycopg2://postgres:*******@predicate-postgresql:5432/postgres"
        - name: PREDICATE_DB_USER
          value: "postgres"
        - name: PREDICATE_DB_PASSWORD
          value: "*******"

        # Celery Manager
        - name: PREDICATE_MANAGER_CELERY_NAME
          value: "predicate-manager"
        - name:  PREDICATE_MANAGER_MAX_RETRIES
          value: "5"
        - name:  PREDICATE_MANAGER_RETRY_BACKOFF
          value: "True"
        - name:  PREDICATE_MANAGER_DEFAULT_RETRY_DELAY
          value: "5"
        - name: PREDICATE_MANAGER_CELERY_CONFIG_PATH
          value: "/app/celery/manager/config.py"

        # Celery Worker
        - name: PREDICATE_WORKER_CELERY_NAME
          value: "predicate-worker"
        - name: PREDICATE_WORKER_MAX_RETRIES
          value: "5"
        - name: PREDICATE_WORKER_RETRY_BACKOFF
          value: "True"
        - name: PREDICATE_WORKER_DEFAULT_RETRY_DELAY
          value: "5"
        - name: PREDICATE_WORKER_LOG_PATH
          value: "/tmp/logs"
        - name: PREDICATE_WORKER_CELERY_CONFIG_PATH
          value: "/app/celery/worker/config.py"

        # Celery Worker JDBC Settings
        - name: PREDICATE_WORKER_DATASOURCE_JDBC_JAVA_HEAP_SIZE_GB
          value: "4"
        - name: PREDICATE_WORKER_DATASOURCE_JDBC_CHUNK_SIZE
          value: "250000"

        # Celery Worker S3 Settings
        - name: PREDICATE_WORKER_DATASOURCE_S3_FILE_CHUNK_SIZE
          value: "20480"  # 20 megabytes
        - name: PREDICATE_WORKER_DATASOURCE_S3_CHUNK_SIZE
          value: "250000"
        - name: PREDICATE_WORKER_DATASOURCE_S3_PARQUET_CHUNK_SIZE_MB
          value: "50"

        # Celery Worker Path
        - name: PREDICATE_WORKER_DATASOURCE_DRIVER_PWD  # default drivers path; override if needed; users can add new ones
          value: "/app/drivers"
        - name: PREDICATE_WORKER_METRIC_PWD  # default metrics path; override if needed; users can add new ones
          value: "/tmp/share"
        - name: PREDICATE_WORKER_TRANSFORM_PWD  # default transform path; override if needed; users can add new ones
          value: "/tmp/share"

        # Executor
        - name: PREDICATE_EXECUTOR_SHARE_DIR  #  store intermediate data for run task execution and dynamic.html charts
          value: "/tmp/executor/share" 
        - name: PREDICATE_EXECUTOR_RESULT_DIR  # save default result charts path 
          value: "/tmp/executor/result"
        - name: PREDICATE_EXECUTOR_TMP_DIR  # store temporary files when creating reports and uploading CSVs to datasource via the UI  
          value: "/tmp/executor/tmp"

        # Keycloak
        - name: KEYCLOAK_URL
          value: "https://auth.k8s.datasapience.ru/auth"
        - name: KEYCLOAK_REALM
          value: "dev"
        - name: KEYCLOAK_CLIENT_ID
          value: "kolmogorov"
        - name: KEYCLOAK_ADMIN_USERNAME
          value: "kolmogorov-sa"
        - name: KEYCLOAK_ADMIN_PASSWORD
          value: "****" # replace to your secret
        - name: KEYCLOAK_ADMIN_GRANT_TYPE
          value: "service"

        # Sentry
        - name: PREDICATE_SENTRY_ENABLED
          value: "False"
        - name: PREDICATE_SENTRY_DSN
          value: ""
        - name: PREDICATE_WORKER_SENTRY_ENABLED
          value: "False"
        - name: PREDICATE_WORKER_SENTRY_DSN
          value: ""

        # Notification
        - name: KLMG_UTILS_NOTIFICATION_SMTP
          value: "False"
        - name: KLMG_UTILS_NOTIFICATION_SMTP_CONFIG_PATH
          value: "/app/notification/smtp.json"

        # Other
        - name: TZ
          value: "Europe/Moscow"

  extra_vars:
    - secret: predicate-secret

  configMaps:
    - name: predicate-celery-manager-config-map
      data:
        - config.py: |
            # QUARTER = 95 DAYS = 8208000 SECONDS
            # settings from retried task after failure
            # https://docs.celeryq.dev/en/stable/userguide/optimizing.html#reserve-one-task-at-a-time
            # task_acks_late = True
            # task_reject_on_worker_lost = True
            # task_acks_on_failure_or_timeout = False
            #
            # warnings
            # broker_connection_retry_on_startup = True
            # worker_cancel_long_running_tasks_on_connection_loss = True
            #
            # settings for long-running tasks
            # https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html#id2
            # broker_transport_options = {"visibility_timeout": QUARTER}
            # result_backend_transport_options = {"visibility_timeout": QUARTER}
            # visibility_timeout = QUARTER

            import os
            from celery.schedules import crontab

            # Broker and Result Backend Configuration
            broker_url = "redis://default:redis@predicate-redis-master:6379"
            result_backend = f"db+postgresql://{os.environ['PREDICATE_DB_USER']}:{os.environ['PREDICATE_DB_PASSWORD']}@predicate-postgresql:5432/postgres?options=-csearch_path=klmg_predicate"
            task_default_queue = "manager"

            # Task Configuration
            task_send_sent_event = True
            result_persistent = True
            result_extended = True
            result_expires = 8208000

            # Task Acknowledgment Settings
            task_acks_late = True
            task_reject_on_worker_lost = True
            task_acks_on_failure_or_timeout = False

            # Connection Settings
            broker_connection_retry_on_startup = True
            worker_cancel_long_running_tasks_on_connection_loss = True

            # Transport Options
            broker_transport_options = {"visibility_timeout": 8208000}
            result_backend_transport_options = {"visibility_timeout": 8208000}
            visibility_timeout = 8208000

            # Timezone Configuration
            enable_utc = False
            timezone = "Europe/Moscow"

            # Beat Schedule Configuration
            beat_schedule = {
                "utils_schedule_guard_every_12h": {
                    "task": "utils::schedule::guard",
                    "schedule": crontab(minute=0, hour="1,13"),  # 1 AM and 1 PM daily
                }
            }

    - name: predicate-celery-worker-config-map
      data:
        - config.py: |    
            import os

            # Broker and Result Backend Configuration
            broker_url = "redis://default:redis@predicate-redis-master:6379"
            result_backend = f"db+postgresql://{os.environ['PREDICATE_DB_USER']}:{os.environ['PREDICATE_DB_PASSWORD']}@predicate-postgresql:5432/postgres?options=-csearch_path=klmg_predicate"
            task_default_queue = "sandbox"
            # "task_reject_on_worker_lost": true,

            # Task Configuration
            task_send_sent_event = True
            result_persistent = True
            result_extended = True
            result_expires = 8208000

            # Task Acknowledgment Settings
            task_acks_late = True
            task_reject_on_worker_lost = False
            task_acks_on_failure_or_timeout = False
            worker_prefetch_multiplier = 1

            # Connection Settings
            broker_connection_retry_on_startup = True
            worker_cancel_long_running_tasks_on_connection_loss = True

            # Transport Options
            broker_transport_options = {"visibility_timeout": 8208000}
            result_backend_transport_options = {"visibility_timeout": 8208000}
            visibility_timeout = 8208000

            # Timezone Configuration
            enable_utc = False
            timezone = "Europe/Moscow"

  persistentVolumes:
    - name: predicate-executor
      mountPath: "/tmp/executor"
      volumeClaim:
        size: 10Gi
        accessMode: ReadWriteMany
        storageClass: nfs
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-metrics
      mountPath: "/tmp/share"
      volumeClaim:
        size: 100Mi
        accessMode: ReadWriteMany
        storageClass: nfs
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-logs
      mountPath: "/tmp/logs"
      volumeClaim:
        size: 5Gi
        accessMode: ReadWriteMany
        storageClass: nfs
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-drivers
      mountPath: "/app/drivers"
      volumeClaim:
        size: 1Gi
        accessMode: ReadWriteMany
        storageClass: nfs
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-prometheus
      mountPath: "/app/prometheus"
      volumeClaim:
        size: 100Mi
        accessMode: ReadWriteMany
        storageClass: nfs
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: celery-manager-config
      configMap: predicate-celery-manager-config-map
      mountPath: /app/celery/manager
      items:
        - key: config.py
          path: config.py
    - name: celery-worker-config
      configMap: predicate-celery-worker-config-map
      mountPath: /app/celery/worker
      items:
        - key: config.py
          path: config.py

  init:
    containers:
      db-init:
          image:
            repository: "{{ .Values.global.image.repository }}"
            tag: "{{ .Values.global.image.tag }}"
            pullPolicy: Always
          command:
            - "python"
          args:
            - "-m"
            - "predicate.services.migration.upgrade"
          resources:
            limits:
              cpu: 256m
              memory: 256Mi
            requests:
              cpu: 100m
              memory: 100Mi

      metric-init:
       image:
         repository: "{{ .Values.global.image.repository }}"
         tag: "{{ .Values.global.image.tag }}"
         pullPolicy: Always
       command:
         - "predicate-script"
       args:
         - "metric"
         - "init"
         - "packages/metrics"
       resources:
         limits:
           cpu: 256m
           memory: 256Mi
         requests:
           cpu: 100m
           memory: 100Mi

      driver-init:
        image:
          repository: "{{ .Values.global.image.repository }}"
          tag: "{{ .Values.global.image.tag }}"
          pullPolicy: Always
        command:
          - "/bin/sh"
        args:
          - "-c"
          - 'for file in /tmp/drivers/*; do if [ -f "$file" ]; then filename=$(basename "$file"); if [ ! -f "/app/drivers/$filename" ]; then cp "$file" "/app/drivers/$filename"; echo "File $filename copy in /app/drivers"; fi; fi; done'
        resources:
          limits:
            cpu: 100m
            memory: 100Mi
          requests:
            cpu: 100m
            memory: 100Mi

  securityContext:
    runAsNonRoot: false
    runAsUser: 9999
    runAsGroup: 9999

  podSecurityContext:
    fsGroup: 9999

  serviceAccount:
    name: predicate-tech
    existing: false
    rules:
      - apiGroups: [""]
        resources: ["limitranges", "resourcequotas", "pods", "namespaces"]
        verbs: ["list", "get", "watch"]
      - apiGroups: [ "apps" ]
        resources: ["statefulsets", "replicasets"]
        verbs: [ "list", "get", "watch" ]
      - apiGroups: [""]
        resources: ["secrets"]
        verbs: ["create", "delete", "get", "list", "patch"]

  livenessProbe:
   httpGet:
     path: /health
     port: 8000

   initialDelaySeconds: 120
   periodSeconds: 60
   timeoutSeconds: 15


worker:
  annotations: # from update pod with new image
    rollme: "{{ randAlphaNum 5 | quote }}"
    dependency: predicate-postgres, predicate-rabbitmq

  deploymentStrategy:
    type: Recreate

  name: predicate-celery-worker
  replicas: 1
  extraLabels:
    application: kolmogorov
    component: predicate
    name: predicate-celery-worker

  image:
    repository: "{{ .Values.global.image.repository }}"
    tag: "{{ .Values.global.image.tag }}"

  ingress:
    enabled: False

  command:
    - /bin/sh
    - -c
  args:
    - |
      celery -A predicate_worker.app:app worker \
      --hostname=$(PREDICATE_WORKER_CELERY_NAME) \
      --loglevel=INFO \
      --concurrency=1 \
      --max-tasks-per-child=10 \
      --task-events
      --statedb=/tmp/celery-worker-db/worker.db 

  resources:
    limits:
      cpu: 2000m
      memory: 7000Mi
    requests:
      cpu: 20m
      memory: 160Mi

  extra_vars:
    - secret: predicate-secret

  persistentVolumes:
    - name: predicate-celery-worker-db
      mountPath: "/tmp/celery-worker-db"
      volumeClaim:
        size: 300Mi
        accessMode: ReadWriteOnce
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-executor
      mountPath: "/tmp/executor"
      existingVolumeClaim: true
    - name: predicate-metrics
      mountPath: "/tmp/share"
      existingVolumeClaim: true
    - name: predicate-logs
      mountPath: "/tmp/logs"
      existingVolumeClaim: true
    - name: predicate-drivers
      mountPath: "/app/drivers"
      existingVolumeClaim: true
    - name: celery-manager-config
      configMap: predicate-celery-manager-config-map
      mountPath: /app/celery/manager
      items:
        - key: config.py
          path: config.py
    - name: celery-worker-config
      configMap: predicate-celery-worker-config-map
      mountPath: /app/celery/worker
      items:
        - key: config.py
          path: config.py


  securityContext:
    runAsNonRoot: false
    runAsUser: 9999
    runAsGroup: 9999

  podSecurityContext:
    fsGroup: 9999

  serviceAccount:
    name: predicate-tech
    existing: true

  livenessProbe:
   exec:
     command:
       - /bin/sh
       - -c
       - "celery -A predicate_worker.app:app inspect ping -d celery@$PREDICATE_WORKER_CELERY_NAME"
   initialDelaySeconds: 120
   periodSeconds: 180
   timeoutSeconds: 120


manager:
  annotations: # from update pod with new image
    rollme: "{{ randAlphaNum 5 | quote }}"
    dependency: predicate-postgres, predicate-rabbitmq

  deploymentStrategy:
    type: Recreate

  name: predicate-celery-manager
  replicas: 1
  image:
    repository: "{{ .Values.global.image.repository }}"
    tag: "{{ .Values.global.image.tag }}"

  extraLabels:
    application: kolmogorov
    component: predicate
    name: predicate-celery-manager

  ingress:
    enabled: False

  command:
    - /bin/sh
    - -c
  args:
    - |
      celery -A predicate.services.manager.app:app worker \
      --hostname=$(PREDICATE_MANAGER_CELERY_NAME) \
      --loglevel=INFO \
      --concurrency=3 \
      --max-tasks-per-child=10 \
      --time-limit=60 \
      --beat \
      --schedule=/tmp/celery-manager-db/schedule.db \
      --task-events \
      --statedb=/tmp/celery-manager-db/manager.db

  resources:
    limits:
      cpu: 1200m
      memory: 1500Mi
    requests:
      cpu: 20m
      memory: 160Mi

  extra_vars:
    - secret: predicate-secret


  persistentVolumes:
    - name: predicate-celery-manager-db
      mountPath: "/tmp/celery-manager-db"
      volumeClaim:
        size: 300Mi
        accessMode: ReadWriteOnce
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-executor
      mountPath: "/tmp/executor"
      existingVolumeClaim: true
    - name: predicate-metrics
      mountPath: "/tmp/share"
      existingVolumeClaim: true
    - name: predicate-logs
      mountPath: "/tmp/logs"
      existingVolumeClaim: true
    - name: predicate-drivers
      mountPath: "/app/drivers"
      existingVolumeClaim: true
    - name: celery-manager-config
      configMap: predicate-celery-manager-config-map
      mountPath: /app/celery/manager
      items:
        - key: config.py
          path: config.py
    - name: celery-worker-config
      configMap: predicate-celery-worker-config-map
      mountPath: /app/celery/worker
      items:
        - key: config.py
          path: config.py

  securityContext:
    runAsNonRoot: false
    runAsUser: 9999
    runAsGroup: 9999

  podSecurityContext:
    fsGroup: 9999

  serviceAccount:
    name: predicate-tech
    existing: true

  livenessProbe:
   exec:
     command:
       - /bin/sh
       - -c
       - "celery -A predicate.services.manager.app:app inspect ping -d celery@$PREDICATE_MANAGER_CELERY_NAME"
   initialDelaySeconds: 120
   periodSeconds: 180
   timeoutSeconds: 120

#  livenessProbe:
#    exec:
#      command:
#        - /bin/sh
#        - -c
#        - |
#          TASK="$(celery -A predicate.services.manager.app:app call utils::health --queue=manager)"
#          RESULT="$(celery -A predicate.services.manager.app:app result $TASK | grep True)"
#
#          if [[ "${RESULT}" == "True" ]]; then
#            echo "Success"
#            exit 0
#          else
#            echo "Error"
#            exit 1
#          fi
#    initialDelaySeconds: 120
#    periodSeconds: 180
#    timeoutSeconds: 120


celery-monitor:
    enabled: true
    name: predicate-celery-monitor
    extraLabels:
      application: kolmogorov
      component: predicate
      name: predicate-celery-monitor

    annotations: # from update pod with new image
      rollme: "{{ randAlphaNum 5 | quote }}"
      dependency: predicate-celery-worker, predicate-celery-manager

    image:
      repository: "{{ .Values.global.image.repository }}"
      tag: "{{ .Values.global.image.tag }}"

    ingress:
      enabled: false

    extra_vars:
      - secret: predicate-secret

    resources:
      limits:
        cpu: 256m
        memory: 400Mi
      requests:
        cpu: 25m
        memory: 96Mi

    command:
      - /bin/sh
      - -c
    args:
      - |
        python -m predicate.services.monitor

    securityContext:
      runAsNonRoot: false

    persistentVolumes:
      - name: predicate-logs
        mountPath: "/tmp/logs"
        existingVolumeClaim: true
      - name: celery-manager-config
        configMap: predicate-celery-manager-config-map
        mountPath: /app/celery/manager
        items:
          - key: config.py
            path: config.py
      - name: celery-worker-config
        configMap: predicate-celery-worker-config-map
        mountPath: /app/celery/worker
        items:
          - key: config.py
            path: config.py

notify:
  enabled: true

  annotations:
    rollme: "{{ randAlphaNum 5 | quote }}"
    dependency: predicate-postgres, predicate-redis

  name: predicate-notify
  replicas: 1

  extraLabels:
    application: kolmogorov
    component: predicate
    name: predicate-notify

  image:
    repository: "{{ .Values.global.image.repository }}"
    tag: "{{ .Values.global.image.tag }}"

  ingress:
    enabled: False

  command:
    - /bin/sh
    - -c
  args:
    - |
      python /app/notify/notify.py

  resources:
    limits:
      cpu: 200m
      memory: 256Mi
    requests:
      cpu: 50m
      memory: 128Mi

  extra_vars:
    - secret: predicate-secret

  configMaps:
    - name: predicate-notification-smtp-config-map
      data:
        - smtp.json: |
            {   
                "smtp_host": "mailhog.mailhog",
                "smtp_port": 1025,
                "from_email": "klmg@example.com",
                "from_name": "KLMG Notification",
                "reply_to": "klmg-reply@example.com"
            }
    - name: predicate-notify-script
      data:
        - notify.py: |
            import logging
            import ast
            from pydantic import TypeAdapter
            from celery import Task
            from celery.events.state import State
            from klmg_utils.models.notification import NotificationService
            from sqlalchemy.orm import Session


            from predicate.core.database.database import database
            from predicate.core.keycloak import keycloak
            from predicate.crud.system.entity.crud import Crud
            from predicate.services.monitor.logger import logger
            from predicate.schemas.entity.subscription.schema import EntitySubscriptionResponse
            from predicate.services.manager.app import app

            TARGETS_TASK = [
                "run::callback::notification",
                "run::callback::notification::error",
            ]


            def main() -> None:
                logging.basicConfig(level=logging.INFO)
                state = State()

                def handler(event):
                    state.event(event)
                    task: Task = state.tasks.get(event["uuid"])

                    if task.name in TARGETS_TASK:
                        if uuid := task.uuid:
                            run_id = int(uuid.rsplit("::run_id=")[1])
                            generate_notify(run_id=run_id, result=ast.literal_eval(task.result))

                with app.connection() as connection:
                    logger.info("Celery monitor started. Listening for task events...")
                    recv = app.events.Receiver(
                        connection,
                        handlers={
                            "task-failed": handler,
                            "task-succeeded": handler,
                            "*": state.event,
                        },
                    )
                    recv.capture(limit=None, timeout=None, wakeup=True)


            @database.with_session
            def generate_notify(session: Session, run_id: int, result: dict):
                crud = Crud(session=session)

                run = crud.run.one(run_id=run_id)
                entity = crud.one_by_id(entity_id=run.entity_id)

                rules = result.get("rules") or {}
                recipients_items = TypeAdapter(list[EntitySubscriptionResponse]).validate_python(
                    result.get("recipients") or []
                )

                recipients_project: set[str] = set()
                recipients_rule: set[str] = set()

                for item in recipients_items:
                    try:
                        sub_obj = (item.subscription_object or "").lower()
                        if item.is_group:
                            groups = keycloak.get_groups(search_value=item.subscriber)
                            for group in groups:
                                members = keycloak.get_group_members([group.get("id")])
                                for member in members:
                                    if email := member.get("email"):
                                        if sub_obj == "project":
                                            recipients_project.add(email)
                                        elif sub_obj == "rule":
                                            recipients_rule.add(email)
                        else:
                            clients = keycloak.get_users(search_value=item.subscriber)
                            for client in clients:
                                if email := client.get("email"):
                                    if sub_obj == "project":
                                        recipients_project.add(email)
                                    elif sub_obj == "rule":
                                        recipients_rule.add(email)
                    except Exception as e:
                        logging.exception(e)


                error_text = result.get("error")
                if error_text:
                    error_recipients = set().union(recipients_project, recipients_rule)
                    if error_recipients:
                        subject_err = f"Project \"{entity.name}\" run failed"
                        message_err = (
                            f"{subject_err}\n\n"
                            f"Run execute date: {run.execute_dt};\n"
                            f"Error: {error_text}\n"
                            "This message was generated automatically."
                        )
                        send_result = NotificationService().send_notification(
                            recipients=list(error_recipients),
                            subject=subject_err,
                            message=message_err,
                        )
                        logging.info(f"Error notification sent successfully: {send_result}")
                    return


                if recipients_project:
                    subject_proj = f"Project \"{entity.name}\" has been completed run with status \"{run.status.value}\""
                    message_proj = (
                        f"{subject_proj}\n\n"
                        f"Run execute date: {run.execute_dt};\n"
                        f"Run status: {run.status_desc}.\n"
                        f"This message was generated automatically."
                    )
                    send_result = NotificationService().send_notification(
                        recipients=list(recipients_project),
                        subject=subject_proj,
                        message=message_proj,
                    )
                    logging.info(f"Project notification sent successfully: {send_result}")


                if recipients_rule:
                    rule_names = ", ".join([str(r.get("key", "")) for r in (rules or [])])
                    subject_rules = (
                        f"Project \"{entity.name}\" has been completed run and rules: [{rule_names}] calculated."
                    )
                    rules_lines = [f"  · {str(r.get('key', ''))}: {str(r.get('value', ''))}" for r in (rules or [])]
                    rules_details = "\n".join(rules_lines)
                    message_rules_text = (
                        f"{subject_rules}\n\n"
                        f"Run execute date: {run.execute_dt};\n"
                        + ("Rules calculated:\n" + rules_details + "\n" if rules_lines else "")
                        + "This message was generated automatically."
                    )
                    send_result = NotificationService().send_notification(
                        recipients=list(recipients_rule),
                        subject=subject_rules,
                        message=message_rules_text,
                    )
                    logging.info(f"Rule notification sent successfully: {send_result}")


            if __name__ == "__main__":
                main()


  persistentVolumes:
    - name: notify-script
      configMap: predicate-notify-script
      mountPath: /app/notify
      items:
        - key: notify.py
          path: notify.py

    - name: celery-manager-config
      configMap: predicate-celery-manager-config-map
      mountPath: /app/celery/manager
      items:
        - key: config.py
          path: config.py
    - name: celery-worker-config
      configMap: predicate-celery-worker-config-map
      mountPath: /app/celery/worker
      items:
        - key: config.py
          path: config.py
    - name: predicate-notification-smtp-config
      configMap: predicate-notification-smtp-config-map
      mountPath: /app/notification
      items:
        - key: smtp.json
          path: smtp.json


flower:
  enabled: true
  replicas: 1
  annotations: # from update pod with new image
    rollme: "{{ randAlphaNum 5 | quote }}"
    dependency: predicate-celery-worker, predicate-celery-manager

  deploymentStrategy:
    type: Recreate

  name: predicate-flower
  extraLabels:
    application: kolmogorov
    component: predicate
    name: predicate-flower
  resources:
    limits:
      cpu: 250m
      memory: 400Mi
    requests:
      cpu: 10m
      memory: 96Mi

  ingress:
    enabled: false
    host: "flower-predicate-dev"
    baseDomain: "k8s.datasapience.ru"

  service:
    port: 5555

  image:
    repository: "{{ .Values.global.image.repository }}"
    tag: "{{ .Values.global.image.tag }}"

  command:
    - /bin/sh
    - -c
  args:
    - |
      celery -A predicate.services.manager.app:app flower \
      --basic-auth=admin:******* \
      --persistent=True \
      --db=/tmp/flower-db/flower.db

  extra_vars:
    - secret: predicate-secret

  persistentVolumes:
    - name: predicate-flower-db
      mountPath: "/tmp/flower-db"
      volumeClaim:
        size: 300Mi
        accessMode: ReadWriteOnce
        annotations:
          helm.sh/resource-policy: keep  # helm delete not delete pvc
    - name: predicate-metrics
      mountPath: "/tmp/share"
      existingVolumeClaim: true
    - name: predicate-logs
      mountPath: "/tmp/logs"
      existingVolumeClaim: true
    - name: predicate-drivers
      mountPath: "/app/drivers"
      existingVolumeClaim: true
    - name: celery-manager-config
      configMap: predicate-celery-manager-config-map
      mountPath: /app/celery/manager
      items:
        - key: config.py
          path: config.py
    - name: celery-worker-config
      configMap: predicate-celery-worker-config-map
      mountPath: /app/celery/worker
      items:
        - key: config.py
          path: config.py

  securityContext:
    runAsNonRoot: false
    runAsUser: 9999
    runAsGroup: 9999

  podSecurityContext:
    fsGroup: 9999

  serviceAccount:
    name: predicate-tech
    existing: true


redis:
  fullnameOverride: predicate-redis
  commonLabels:
    application: kolmogorov
    component: predicate
    name: predicate-redis
  auth:
    enabled: false
    password: redis

  image:
    tag: 7.2.4-debian-11-r5

  master:
    configuration: |-
      appendonly yes
      appendfsync everysec
    persistence:
      enabled: true
      storageClass: nfs
      size: 2Gi
    extraEnvVars:
      - name: TZ
        value: "Europe/Moscow"
    resources:
      limits:
        cpu: 150m
        memory: 150Mi
      requests:
        cpu: 25m
        memory: 64Mi

  replica:
    replicaCount: 0
    resources:
      limits:
        cpu: 150m
        memory: 150Mi
      requests:
        cpu: 25m
        memory: 64Mi
  architecture: replication


rabbitmq:
    enabled: false
    fullnameOverride: predicate-rabbitmq
    podLabels:
      application: kolmogorov
      component: predicate
      name: predicate-rabbitmq
    service:
      labels:
        application: kolmogorov
        component: predicate
        name: predicate-rabbitmq

    image:
      tag: 3.13.3

    ingress:
      enabled: false
      className: "nginx"
      annotations:
        kubernetes.io/ingress.class: "nginx"

      hosts:
        - host: "rabbitmq-predicate-dev.k8s.datasapience.ru"
          paths:
            - path: /
              pathType: Prefix
      tls:
        - hosts:
            - "rabbitmq-predicate-dev.k8s.datasapience.ru"
          secretName: dev-wildcard
    env:
      - name: TZ
        value: "Europe/Moscow"
      - name: RABBITMQ_DEFAULT_USER
        value: admin
      - name: RABBITMQ_DEFAULT_PASS
        value: *******
      - name: ERLANG_COOKIE
        value: i0kS5smlEmWMAGcXVSaog5EL4ybyHWRP:po

    replicaCount: 1

    customConfig: |-
      consumer_timeout = 8208000000  # 95 days \ quarter

    storage:
     className: "yc-network-hdd"
     requestedSize: 5Gi

    initResources:
      limits:
        cpu: 100m
        memory: 128Mi
      requests:
        cpu: 10m
        memory: 64Mi

    resources:
      limits:
          cpu: 700m
          memory: 600Mi
      requests:
          cpu: 50m
          memory: 200Mi

#    startupProbe:
#      enabled: false


postgresql:
  fullnameOverride: predicate-postgresql
  commonLabels:
    application: kolmogorov
    component: predicate
    name: predicate-postgresql
  global:
    security:
      allowInsecureImages: true
  auth:
    postgresPassword: *******
    username: postgres
    password: *******
    database: postgres
  primary:
    extraEnvVars:
      - name: TZ
        value: "Europe/Moscow"
    service:
      type: NodePort
    persistence:
      size: 10Gi
    resources:
      limits:
        cpu: 500m
        memory: 500Mi
      requests:
        cpu: 10m
        memory: 64Mi
    initdb:
     scripts:
       init_schema.sh: |
         #!/bin/sh
         PGPASSWORD=******* psql -U postgres -d postgres -c 'CREATE SCHEMA IF NOT EXISTS klmg_predicate'