diff --git a/charts/helmbroker/Chart.yaml b/charts/helmbroker/Chart.yaml index 6e22e3a..6e8d2aa 100644 --- a/charts/helmbroker/Chart.yaml +++ b/charts/helmbroker/Chart.yaml @@ -9,6 +9,7 @@ dependencies: - name: valkey repository: {{repository}} version: x.x.x + condition: valkey.enabled description: Drycc Workflow helmbroker. maintainers: - name: Drycc Team diff --git a/charts/helmbroker/templates/_helpers.tpl b/charts/helmbroker/templates/_helpers.tpl index 4702bc6..3e9e40f 100644 --- a/charts/helmbroker/templates/_helpers.tpl +++ b/charts/helmbroker/templates/_helpers.tpl @@ -13,14 +13,14 @@ env: secretKeyRef: name: helmbroker-creds key: valkey-url -{{- else if eq .Values.global.valkeyLocation "on-cluster" }} +{{- else if .Values.valkey.enabled }} - name: VALKEY_PASSWORD valueFrom: secretKeyRef: name: valkey-creds key: password - name: HELMBROKER_VALKEY_URL - value: "redis://:$(VALKEY_PASSWORD)@drycc-valkey.{{.Release.Namespace}}.svc.{{.Values.global.clusterDomain}}:26379/0?master_set=drycc" + value: "redis://:$(VALKEY_PASSWORD)@drycc-valkey:26379/0?master_set=drycc" {{- end }} {{- range $key, $value := .Values.environment }} - name: {{ $key }} @@ -28,20 +28,6 @@ env: {{- end }} {{- end }} -{{/* Generate helmbroker deployment limits */}} -{{- define "helmbroker.limits" -}} -{{- if or (.Values.limitsCpu) (.Values.limitsMemory) }} -resources: - limits: -{{- if (.Values.limitsCpu) }} - cpu: {{.Values.limitsCpu}} -{{- end }} -{{- if (.Values.limitsMemory) }} - memory: {{.Values.limitsMemory}} -{{- end }} -{{- end }} -{{- end }} - {{/* Generate helmbroker deployment volumeMounts */}} {{- define "helmbroker.volumeMounts" }} volumeMounts: diff --git a/charts/helmbroker/templates/helmbroker-celery-deployment.yaml b/charts/helmbroker/templates/helmbroker-celery-deployment.yaml index 7388b2c..1b39b78 100644 --- a/charts/helmbroker/templates/helmbroker-celery-deployment.yaml +++ b/charts/helmbroker/templates/helmbroker-celery-deployment.yaml @@ -7,7 +7,7 @@ metadata: annotations: component.drycc.cc/version: {{ .Values.imageTag }} spec: - replicas: {{ .Values.celeryReplicas }} + replicas: {{ .Values.celery.replicas }} strategy: rollingUpdate: maxSurge: 1 @@ -33,11 +33,12 @@ spec: args: - netcat - -v - - -a - - $(DRYCC_HELMBROKER_SERVICE_HOST):$(DRYCC_HELMBROKER_SERVICE_PORT) + - -u + - http://drycc-helmbroker {{- include "helmbroker.envs" . | indent 10 }} containers: - - name: drycc-helmbroker-celery + {{- range $key := (list "low" "middle" "high") }} + - name: drycc-helmbroker-celery-{{$key}} image: {{$.Values.imageRegistry}}/{{$.Values.imageOrg}}/helmbroker:{{$.Values.imageTag}} imagePullPolicy: {{$.Values.imagePullPolicy}} {{- if $.Values.diagnosticMode.enabled }} @@ -47,9 +48,17 @@ spec: args: - /bin/bash - -c - - celery --app helmbroker worker --queues helmbroker.low,helmbroker.middle,helmbroker.high --autoscale=32,1 --loglevel=WARNING + - celery --app helmbroker worker -n {{uuidv4}}@%h --queues helmbroker.{{$key}} --autoscale=32,1 --loglevel=WARNING + {{- end }} + {{- with index $.Values "celery" "resources" }} + resources: + {{- toYaml . | nindent 10 }} {{- end }} - {{- include "helmbroker.limits" $ | indent 8 }} {{- include "helmbroker.envs" $ | indent 8 }} {{- include "helmbroker.volumeMounts" $ | indent 8 }} + {{- end }} {{- include "helmbroker.volumes" . | indent 6 }} + securityContext: + fsGroup: 1001 + runAsGroup: 1001 + runAsUser: 1001 diff --git a/charts/helmbroker/templates/helmbroker-deployment.yaml b/charts/helmbroker/templates/helmbroker-deployment.yaml index 6ad1385..45cc4cb 100644 --- a/charts/helmbroker/templates/helmbroker-deployment.yaml +++ b/charts/helmbroker/templates/helmbroker-deployment.yaml @@ -7,7 +7,7 @@ metadata: annotations: component.drycc.cc/version: {{ .Values.imageTag }} spec: - replicas: {{ .Values.replicas }} + replicas: {{ .Values.api.replicas }} strategy: rollingUpdate: maxSurge: 1 @@ -71,7 +71,14 @@ spec: ports: - containerPort: 8000 name: http - {{- include "helmbroker.limits" . | indent 8 }} + {{- with index .Values "api" "resources" }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} {{- include "helmbroker.envs" . | indent 8 }} {{- include "helmbroker.volumeMounts" . | indent 8 }} {{- include "helmbroker.volumes" . | indent 6 }} + securityContext: + fsGroup: 1001 + runAsGroup: 1001 + runAsUser: 1001 diff --git a/charts/helmbroker/values.yaml b/charts/helmbroker/values.yaml index 60915f7..65b9f54 100644 --- a/charts/helmbroker/values.yaml +++ b/charts/helmbroker/values.yaml @@ -2,9 +2,7 @@ imageOrg: "drycc-addons" imageTag: "canary" imageRegistry: "registry.drycc.cc" imagePullPolicy: "Always" -replicas: 1 -# limitsCpu: "100m" -# limitsMemory: "50Mi" + ## Enable diagnostic mode ## @@ -26,8 +24,6 @@ repositories: - name: drycc-helm-broker url: https://github.com/drycc/addons/releases/download/latest/index.yaml -celeryReplicas: 1 - # broker_credentials: # Optional Usernames and passwords that will be required to communicate with service broker username: admin @@ -44,6 +40,14 @@ environment: # HELMBROKER_CONFIG_ROOT: /etc/helmbroker api: + replicas: 1 + resources: {} + # limits: + # cpu: 200m + # memory: 50Mi + # requests: + # cpu: 100m + # memory: 30Mi nodeAffinityPreset: key: "drycc.cc/node" type: "soft" @@ -59,6 +63,14 @@ api: app: "drycc-helmbroker" celery: + replicas: 1 + resources: {} + # limits: + # cpu: 200m + # memory: 50Mi + # requests: + # cpu: 100m + # memory: 30Mi nodeAffinityPreset: key: "drycc.cc/node" type: "soft" @@ -83,20 +95,5 @@ persistence: storageClass: "" volumeName: "" -global: - # Set the location of Workflow's valkey instance - # Valid values are: - # - on-cluster: Run Valkey within the Kubernetes cluster - # - off-cluster: Run Valkey outside the Kubernetes cluster (configure in controller section) - valkeyLocation: "on-cluster" - # Enable usage of RBAC authorization mode - # - # Valid values are: - # - true: all RBAC-related manifests will be installed (in case your cluster supports RBAC) - # - false: no RBAC-related manifests will be installed - rbac: true - # A domain name consists of one or more parts. - # Periods (.) are used to separate these parts. - # Each part must be 1 to 63 characters in length and can contain lowercase letters, digits, and hyphens (-). - # It must start and end with a lowercase letter or digit. - clusterDomain: "cluster.local" +valkey: + enabled: true diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile index b6dad30..85d3134 100644 --- a/rootfs/Dockerfile +++ b/rootfs/Dockerfile @@ -4,9 +4,9 @@ FROM registry.drycc.cc/drycc/base:${CODENAME} ENV DRYCC_UID=1001 \ DRYCC_GID=1001 \ DRYCC_HOME_DIR=/workspace \ - PYTHON_VERSION="3.12" \ - HELM_VERSION="3.17.2" \ - KUBECTL_VERSION="1.32.3" + PYTHON_VERSION="3.14" \ + HELM_VERSION="4.1.3" \ + KUBECTL_VERSION="1.35.3" RUN groupadd drycc --gid ${DRYCC_GID} \ && useradd drycc -u ${DRYCC_UID} -g ${DRYCC_GID} -s /bin/bash -m -d ${DRYCC_HOME_DIR} diff --git a/rootfs/Dockerfile.test b/rootfs/Dockerfile.test index 7596355..fe48d53 100644 --- a/rootfs/Dockerfile.test +++ b/rootfs/Dockerfile.test @@ -4,9 +4,9 @@ FROM registry.drycc.cc/drycc/base:${CODENAME} ENV DRYCC_UID=1001 \ DRYCC_GID=1001 \ DRYCC_HOME_DIR=/workspace \ - PYTHON_VERSION="3.12" \ - HELM_VERSION="3.17.2" \ - KUBECTL_VERSION="1.32.3" + PYTHON_VERSION="3.14" \ + HELM_VERSION="4.1.3" \ + KUBECTL_VERSION="1.35.3" RUN groupadd drycc --gid ${DRYCC_GID} \ && useradd drycc -u ${DRYCC_UID} -g ${DRYCC_GID} -s /bin/bash -m -d ${DRYCC_HOME_DIR} diff --git a/rootfs/helmbroker/broker.py b/rootfs/helmbroker/broker.py index c9bfa9a..c920789 100644 --- a/rootfs/helmbroker/broker.py +++ b/rootfs/helmbroker/broker.py @@ -13,7 +13,7 @@ UpdateDetails, UpdateServiceSpec, DeprovisionDetails, \ DeprovisionServiceSpec, LastOperation, OperationState -from .utils import verify_parameters, new_instance_lock +from .utils import verify_parameters, new_instance_lock, verify_parameters_by_plan from .database.fetch import fetch_chart_plan from .database.query import get_instance_path, get_chart_path, get_plan_path, \ get_addon_updateable, get_addon_bindable, get_addon_allow_params, \ @@ -21,6 +21,7 @@ from .database.metadata import load_instance_meta, load_binding_meta, load_addons_meta, \ save_instance_meta from .tasks import provision, bind, deprovision, update, unbind +from .config import INSTANCE_NAME_LENS logger = logging.getLogger(__name__) @@ -44,6 +45,10 @@ def provision(self, async_allowed: bool, **kwargs) -> ProvisionedServiceSpec: logger.debug(f"*** provision instance {instance_id}") + # verify instance_name length + if len(details.context["instance_name"]) > INSTANCE_NAME_LENS: + raise ErrBadRequest( + msg=f"The length of the instance name cannot exceed {INSTANCE_NAME_LENS}.") instance_path = get_instance_path(instance_id) if os.path.exists(instance_path): raise ErrInstanceAlreadyExists() @@ -64,6 +69,10 @@ def provision(self, os.makedirs(instance_path, exist_ok=True) chart_path, plan_path = get_chart_path(instance_id), get_plan_path(instance_id) fetch_chart_plan(details.service_id, chart_path, details.plan_id, plan_path) + # verify instance-schema + msg = verify_parameters_by_plan(instance_id, details.parameters) + if msg: + raise ErrBadRequest(msg) provision.delay(instance_id, details) return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC) @@ -146,6 +155,10 @@ def update(self, if details.plan_id is not None: chart_path, plan_path = get_chart_path(instance_id), get_plan_path(instance_id) fetch_chart_plan(details.service_id, chart_path, details.plan_id, plan_path) + # verify instance-schema + msg = verify_parameters_by_plan(instance_id, details.parameters) + if msg: + raise ErrBadRequest(msg) data = load_instance_meta(instance_id) data['last_operation']["state"] = OperationState.IN_PROGRESS.value data['last_operation']["description"] = ( diff --git a/rootfs/helmbroker/config.py b/rootfs/helmbroker/config.py index e5becfa..c83ad94 100644 --- a/rootfs/helmbroker/config.py +++ b/rootfs/helmbroker/config.py @@ -11,6 +11,7 @@ PASSWORD = os.environ.get('HELMBROKER_PASSWORD') VALKEY_URL = os.environ.get("HELMBROKER_VALKEY_URL", 'redis://localhost:6379/0') +INSTANCE_NAME_LENS = int(os.environ.get("INSTANCE_NAME_LENS", '32')) class Config: diff --git a/rootfs/helmbroker/database/query.py b/rootfs/helmbroker/database/query.py index b7aa331..741cfc3 100644 --- a/rootfs/helmbroker/database/query.py +++ b/rootfs/helmbroker/database/query.py @@ -22,6 +22,10 @@ def get_plan_path(instance_id): return os.path.join(get_instance_path(instance_id), "plan") +def get_plan_schema_path(instance_id): + return os.path.join(get_instance_path(instance_id), "plan", "instance-schema.json") + + def get_hooks_path(instance_id): return os.path.join(get_plan_path(instance_id), "hooks") diff --git a/rootfs/helmbroker/utils.py b/rootfs/helmbroker/utils.py index b76e1b4..e749e85 100644 --- a/rootfs/helmbroker/utils.py +++ b/rootfs/helmbroker/utils.py @@ -5,6 +5,7 @@ import base64 import copy import logging +import jsonschema from urllib.parse import urlparse, parse_qs from contextlib import contextmanager from redis.client import Redis @@ -168,3 +169,52 @@ def _verify_required_parameters(allow_parameters, parameters): if error: error_parameters.add(allow_parameter["name"]) return error_parameters + + +def verify_parameters_by_plan(instance_id, parameters): + """verify parameters allowed or not""" + if not parameters: + return "" + # read schema file + from .database.query import get_plan_schema_path + schema_file = get_plan_schema_path(instance_id) + try: + with open(schema_file, 'r') as f: + schema = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return "" + if not schema: + return "" + # get parameters + if "rawValues" in parameters: + params = yaml.safe_load(base64.b64decode(parameters["rawValues"])) + else: + params = _convert_to_nested_dict(parameters) + # validate schema + try: + jsonschema.validate(params, schema) + except jsonschema.ValidationError as e: + return f"could not validate: {e.message}" + return "" + + +def _convert_to_nested_dict(assignments): + """ + {"a.b.c": "1Gi", "a.b.d": "2Gi"} + -> + {'a': {'b': {'c': '1Gi', 'd': '2Gi'}}} + """ + def set_nested_value(d, keys, value): + if len(keys) == 1: + d[keys[0]] = value + else: + if keys[0] not in d: + d[keys[0]] = {} + set_nested_value(d[keys[0]], keys[1:], value) + result = {} + if isinstance(assignments, dict): + # dict format: {"a.b.c": "1Gi"} + for key_path, value in assignments.items(): + keys = key_path.split('.') + set_nested_value(result, keys, value) + return result diff --git a/rootfs/helmbroker/wsgi.py b/rootfs/helmbroker/wsgi.py index 64db221..206c711 100644 --- a/rootfs/helmbroker/wsgi.py +++ b/rootfs/helmbroker/wsgi.py @@ -18,8 +18,7 @@ def readiness(): if "KUBECONFIG" in os.environ: return "OK" elif "KUBERNETES_SERVICE_PORT" in os.environ and \ - ("KUBERNETES_SERVICE_HOST" in os.environ or - "KUBERNETES_CLUSTER_DOMAIN" in os.environ): + "KUBERNETES_SERVICE_HOST" in os.environ: return "OK" return make_response("kubernetes not available", 500) diff --git a/rootfs/requirements.txt b/rootfs/requirements.txt index df9e44a..7810104 100644 --- a/rootfs/requirements.txt +++ b/rootfs/requirements.txt @@ -1,7 +1,7 @@ PyYAML==6.0.2 gunicorn==23.0.0 openbrokerapi==4.7.1 -requests==2.32.2 -celery==5.4.0 -redis==5.2.0 -jsonschema==4.23.0 +requests==2.32.5 +celery==5.5.3 +redis==6.4.0 +jsonschema==4.25.1