diff --git a/deployment-configuration/helm/templates/auto-database-postgres-operator.yaml b/deployment-configuration/helm/templates/auto-database-postgres-operator.yaml new file mode 100644 index 00000000..25d380b0 --- /dev/null +++ b/deployment-configuration/helm/templates/auto-database-postgres-operator.yaml @@ -0,0 +1,86 @@ +{{- define "deploy_utils.database.postgres.operator" }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-credentials" .app.harness.database.name | quote }} + namespace: {{ .root.Values.namespace }} + labels: + app: {{ .app.harness.database.name | quote }} +{{ include "deploy_utils.labels" .root | indent 4 }} +type: kubernetes.io/basic-auth +stringData: + username: {{ .app.harness.database.user | quote }} + password: {{ .app.harness.database.pass | quote }} +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ .app.harness.database.name | quote }} + namespace: {{ .root.Values.namespace }} + labels: + app: {{ .app.harness.database.name | quote }} +{{ include "deploy_utils.labels" .root | indent 4 }} +spec: + instances: {{ .app.harness.database.postgres.instances | default 1 }} + + inheritedMetadata: + labels: + app: {{ .app.harness.database.name | quote }} + service: db + + bootstrap: + initdb: + database: {{ .app.harness.database.postgres.initialdb | quote }} + owner: {{ .app.harness.database.user | quote }} + secret: + name: {{ printf "%s-credentials" .app.harness.database.name | quote }} + + storage: + size: {{ .app.harness.database.size }} + + {{- with .app.harness.database.resources }} + resources: + {{- with .requests }} + requests: + {{- with .memory }} + memory: {{ . | quote }} + {{- end }} + {{- with .cpu }} + cpu: {{ . | quote }} + {{- end }} + {{- end }} + {{- with .limits }} + limits: + {{- with .memory }} + memory: {{ . | quote }} + {{- end }} + {{- end }} + {{- end }} + + {{- if .app.harness.database.image_ref }} + imageName: {{ index (index .app "task-images") .app.harness.database.image_ref | default ("Image ref not found!" | quote) }} + {{- else if .app.harness.database.postgres.image }} + imageName: {{ .app.harness.database.postgres.image | quote }} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .app.harness.database.name | quote }} + namespace: {{ .root.Values.namespace }} + labels: + app: {{ .app.harness.deployment.name | quote }} +{{ include "deploy_utils.labels" .root | indent 4 }} +spec: + type: {{ if .app.harness.database.expose }}LoadBalancer{{ else }}ClusterIP{{ end }} + selector: + app: {{ .app.harness.database.name | quote }} + cnpg.io/instanceRole: primary + ports: + {{- range $port := .app.harness.database.postgres.ports }} + - name: {{ $port.name }} + port: {{ $port.port }} + targetPort: 5432 + {{- end }} +{{- end }} diff --git a/deployment-configuration/helm/templates/auto-database.yaml b/deployment-configuration/helm/templates/auto-database.yaml index 212d763f..a23e97a6 100644 --- a/deployment-configuration/helm/templates/auto-database.yaml +++ b/deployment-configuration/helm/templates/auto-database.yaml @@ -14,6 +14,9 @@ spec: --- {{- end }} {{- define "deploy_utils.database" }} +{{- if and (eq .app.harness.database.type "postgres") .app.harness.database.postgres.operator }} +{{- include "deploy_utils.database.postgres.operator" . }} +{{- else }} --- kind: PersistentVolumeClaim apiVersion: v1 @@ -105,6 +108,7 @@ spec: - name: {{ $port.name }} port: {{ $port.port }} {{- end }} +{{- end }} --- {{- include "deploy_utils.database_network_policy" (dict "root" .root "app" .app) }} {{ end }} diff --git a/deployment-configuration/helm/templates/auto-network-policies.yaml b/deployment-configuration/helm/templates/auto-network-policies.yaml index 6dbf4eb0..63f5dc4c 100644 --- a/deployment-configuration/helm/templates/auto-network-policies.yaml +++ b/deployment-configuration/helm/templates/auto-network-policies.yaml @@ -117,6 +117,38 @@ spec: protocol: UDP - port: 53 protocol: TCP + {{- if and (eq .app.harness.database.type "postgres") .app.harness.database.postgres.operator }} + # Allow CNPG pods to reach the Kubernetes API server + {{- $apiCidrs := list }} + {{- $kubeSvc := (lookup "v1" "Service" "default" "kubernetes") }} + {{- $kubeEp := (lookup "v1" "Endpoints" "default" "kubernetes") }} + {{- if $kubeSvc }} + {{- if $kubeSvc.spec }} + {{- if $kubeSvc.spec.clusterIP }} + {{- $apiCidrs = append $apiCidrs (printf "%s/32" $kubeSvc.spec.clusterIP) }} + {{- end }} + {{- end }} + {{- end }} + {{- if and $kubeEp $kubeEp.subsets }} + {{- range $subset := $kubeEp.subsets }} + {{- range $addr := $subset.addresses }} + {{- $apiCidrs = append $apiCidrs (printf "%s/32" $addr.ip) }} + {{- end }} + {{- end }} + {{- end }} + {{- /* Fall back to configured CIDRs when lookup returns nothing (e.g. helm template) */ -}} + {{- if not $apiCidrs }} + {{- $apiCidrs = .app.harness.database.postgres.apiServerCidr }} + {{- end }} + {{- range $cidr := $apiCidrs }} + - to: + - ipBlock: + cidr: {{ $cidr }} + ports: + - port: 443 + protocol: TCP + {{- end }} + {{- end }} {{- range $ns := $allowedNamespaces }} # Allow traffic to whitelisted namespace - to: diff --git a/deployment-configuration/value-template.yaml b/deployment-configuration/value-template.yaml index fef6f5a8..fa34ca0a 100644 --- a/deployment-configuration/value-template.yaml +++ b/deployment-configuration/value-template.yaml @@ -95,8 +95,15 @@ harness: port: 27017 # -- settings for postgers database (for type==postgres) postgres: - image: postgres:13 + image: initialdb: cloudharness + # -- Use the CloudNative-PG operator instead of a plain Deployment. Requires the CNPG operator to be installed in the cluster. + operator: false + # -- Number of PostgreSQL instances managed by the CNPG operator (only used when operator is true) + instances: 1 + # -- CIDR(s) allowed for CNPG pods to reach the Kubernetes API server (port 443). + # -- Resolved automatically at deploy time via cluster lookup. Set explicitly only as a fallback for helm-template or air-gapped use. + apiServerCidr: [] ports: - name: http port: 5432 diff --git a/deployment/codefresh-test.yaml b/deployment/codefresh-test.yaml index b4ad6b8d..1e418c0c 100644 --- a/deployment/codefresh-test.yaml +++ b/deployment/codefresh-test.yaml @@ -393,7 +393,7 @@ steps: samples: type: build stage: build - dockerfile: Dockerfile + dockerfile: test.Dockerfile registry: '${{CODEFRESH_REGISTRY}}' buildkit: true build_arguments: @@ -522,13 +522,13 @@ steps: commands: - kubectl config use-context ${{CLUSTER_NAME}} - kubectl config set-context --current --namespace=test-${{NAMESPACE_BASENAME}} + - kubectl rollout status deployment/workflows - kubectl rollout status deployment/samples - kubectl rollout status deployment/samples-gk - - kubectl rollout status deployment/workflows - - kubectl rollout status deployment/common - - kubectl rollout status deployment/accounts - kubectl rollout status deployment/volumemanager - kubectl rollout status deployment/argo-gk + - kubectl rollout status deployment/common + - kubectl rollout status deployment/accounts - sleep 60 tests_api: stage: qa @@ -539,22 +539,16 @@ steps: commands: - echo $APP_NAME scale: - samples_api_test: - title: samples api test + workflows_api_test: + title: workflows api test volumes: - - '${{CF_REPO_NAME}}/applications/samples:/home/test' + - '${{CF_REPO_NAME}}/applications/workflows:/home/test' - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' environment: - - APP_URL=https://samples.${{DOMAIN}}/api - - USERNAME=sample@testuser.com - - PASSWORD=test + - APP_URL=https://workflows.${{DOMAIN}}/api commands: - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url - https://samples.${{DOMAIN}}/api -c all --skip-deprecated-operations --exclude-operation-id=submit_sync - --exclude-operation-id=submit_sync_with_results --exclude-operation-id=error - --hypothesis-suppress-health-check=too_slow --hypothesis-deadline=180000 - --request-timeout=180000 --hypothesis-max-examples=2 --show-trace --exclude-checks=ignored_auth - - pytest -v test/api + https://workflows.${{DOMAIN}}/api -c all common_api_test: title: common api test volumes: @@ -565,16 +559,22 @@ steps: commands: - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url https://common.${{DOMAIN}}/api -c all - workflows_api_test: - title: workflows api test + samples_api_test: + title: samples api test volumes: - - '${{CF_REPO_NAME}}/applications/workflows:/home/test' + - '${{CF_REPO_NAME}}/applications/samples:/home/test' - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' environment: - - APP_URL=https://workflows.${{DOMAIN}}/api + - APP_URL=https://samples.${{DOMAIN}}/api + - USERNAME=sample@testuser.com + - PASSWORD=test commands: - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url - https://workflows.${{DOMAIN}}/api -c all + https://samples.${{DOMAIN}}/api -c all --skip-deprecated-operations --exclude-operation-id=submit_sync + --exclude-operation-id=submit_sync_with_results --exclude-operation-id=error + --hypothesis-suppress-health-check=too_slow --hypothesis-deadline=180000 + --request-timeout=180000 --hypothesis-max-examples=2 --show-trace --exclude-checks=ignored_auth + - pytest -v test/api hooks: on_fail: exec: diff --git a/docs/applications/databases.md b/docs/applications/databases.md index 96498f6c..3704c3f2 100644 --- a/docs/applications/databases.md +++ b/docs/applications/databases.md @@ -84,6 +84,9 @@ harness postgres: image: postgres:13 initialdb: cloudharness + operator: false + instances: 1 + apiServerCidr: [] ports: - name: http port: 5432 @@ -91,6 +94,19 @@ harness `initialdb` is the default database used +`operator`: When set to `true`, uses the [CloudNative-PG operator](https://github.com/cloudnative-pg/cloudnative-pg) instead of a plain Kubernetes Deployment. This provides advanced features like automated failover and cluster management. **Backups are not configured by default by this chart; you must define CNPG backup resources (for example, `Backup` and/or `ScheduledBackup` objects) or use another backup mechanism separately.** **Requires the CNPG operator to be pre-installed in the cluster.** + +To install the CNPG operator: +```bash +helm repo add cloudnative-pg https://cloudnative-pg.github.io/charts +helm repo update +helm install cnpg cloudnative-pg/cloudnative-pg +``` + +`instances`: Number of PostgreSQL instances (replicas) managed by the CNPG operator. Only used when `operator: true`. Default is 1. + +`apiServerCidr`: List of CIDRs allowed for CNPG database pods to reach the Kubernetes API server on port 443. **Resolved automatically at deploy time** by looking up the `kubernetes` Service and Endpoints in the `default` namespace. The explicit list is only used as a fallback when lookup returns nothing (e.g. `helm template` dry-run). Leave empty (`[]`) for auto-detection; set explicitly only for air-gapped or restricted environments. + #### Neo4j diff --git a/docs/model/DatabaseConfig.md b/docs/model/DatabaseConfig.md index c6b3bf21..4857965e 100644 --- a/docs/model/DatabaseConfig.md +++ b/docs/model/DatabaseConfig.md @@ -9,6 +9,10 @@ Name | Type | Description | Notes **image** | **str** | | [optional] **name** | **str** | | [optional] **ports** | [**List[PortConfig]**](PortConfig.md) | | [optional] +**operator** | **bool** | Use the CloudNative-PG operator instead of a plain Deployment (postgres only) | [optional] +**instances** | **int** | Number of PostgreSQL instances managed by the CNPG operator (only used when operator is true) | [optional] +**api_server_cidr** | **List[str]** | CIDR(s) allowed for CNPG pods to reach the Kubernetes API server (port 443). Override with your cluster API-server or service CIDR. | [optional] +**initialdb** | **str** | Initial database name (postgres only) | [optional] ## Example diff --git a/libraries/models/api/openapi.yaml b/libraries/models/api/openapi.yaml index 52b013fa..b9e7f8bc 100644 --- a/libraries/models/api/openapi.yaml +++ b/libraries/models/api/openapi.yaml @@ -950,6 +950,21 @@ components: type: array items: $ref: '#/components/schemas/PortConfig' + operator: + description: 'Use the CloudNative-PG operator instead of a plain Deployment (postgres only)' + type: boolean + instances: + description: 'Number of PostgreSQL instances managed by the CNPG operator (only used when operator is true)' + type: integer + minimum: 1 + apiServerCidr: + description: 'CIDR(s) allowed for CNPG pods to reach the Kubernetes API server (port 443). Override with your cluster API-server or service CIDR.' + type: array + items: + type: string + initialdb: + description: 'Initial database name (postgres only)' + type: string additionalProperties: true NetworkConfig: title: Root Type for NetworkConfig diff --git a/libraries/models/cloudharness_model/models/database_config.py b/libraries/models/cloudharness_model/models/database_config.py index a2a914c5..79a62bbd 100644 --- a/libraries/models/cloudharness_model/models/database_config.py +++ b/libraries/models/cloudharness_model/models/database_config.py @@ -34,8 +34,12 @@ class DatabaseConfig(CloudHarnessBaseModel): image: Optional[StrictStr] = None name: Optional[StrictStr] = None ports: Optional[List[PortConfig]] = None + operator: Optional[StrictBool] = Field(default=None, description="Use the CloudNative-PG operator instead of a plain Deployment (postgres only)") + instances: Optional[Annotated[int, Field(strict=True, ge=1)]] = Field(default=None, description="Number of PostgreSQL instances managed by the CNPG operator (only used when operator is true)") + api_server_cidr: Optional[List[StrictStr]] = Field(default=None, description="CIDR(s) allowed for CNPG pods to reach the Kubernetes API server (port 443). Override with your cluster API-server or service CIDR.", alias="apiServerCidr") + initialdb: Optional[StrictStr] = Field(default=None, description="Initial database name (postgres only)") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["image", "name", "ports"] + __properties: ClassVar[List[str]] = ["image", "name", "ports", "operator", "instances", "apiServerCidr", "initialdb"] def to_dict(self) -> Dict[str, Any]: """Return the dictionary representation of the model using alias. @@ -83,7 +87,11 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: _obj = cls.model_validate({ "image": obj.get("image"), "name": obj.get("name"), - "ports": [PortConfig.from_dict(_item) for _item in obj["ports"]] if obj.get("ports") is not None else None + "ports": [PortConfig.from_dict(_item) for _item in obj["ports"]] if obj.get("ports") is not None else None, + "operator": obj.get("operator"), + "instances": obj.get("instances"), + "apiServerCidr": obj.get("apiServerCidr"), + "initialdb": obj.get("initialdb") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/user.py b/libraries/models/cloudharness_model/models/user.py index 1382348c..bfc2a10f 100644 --- a/libraries/models/cloudharness_model/models/user.py +++ b/libraries/models/cloudharness_model/models/user.py @@ -13,24 +13,26 @@ from __future__ import annotations +import pprint +import re # noqa: F401 +import json from typing import Optional, Set from typing_extensions import Self from cloudharness_model.base_model import CloudHarnessBaseModel -from pydantic import Field, StrictStr, StrictBool, StrictInt, StrictFloat -from typing import ClassVar, List, Dict, Any, Optional, Annotated - +from pydantic import BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib from cloudharness_model.models.organization import Organization from cloudharness_model.models.user_credential import UserCredential from cloudharness_model.models.user_group import UserGroup - class User(CloudHarnessBaseModel): """ User - """ # noqa: E501 + """ # noqa: E501 access: Optional[Dict[str, Any]] = None attributes: Optional[Dict[str, Any]] = None client_roles: Optional[Dict[str, Any]] = Field(default=None, alias="clientRoles") @@ -146,3 +148,5 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: _obj.additional_properties[_key] = obj.get(_key) return _obj + + diff --git a/libraries/models/docs/DatabaseConfig.md b/libraries/models/docs/DatabaseConfig.md index c6b3bf21..4857965e 100644 --- a/libraries/models/docs/DatabaseConfig.md +++ b/libraries/models/docs/DatabaseConfig.md @@ -9,6 +9,10 @@ Name | Type | Description | Notes **image** | **str** | | [optional] **name** | **str** | | [optional] **ports** | [**List[PortConfig]**](PortConfig.md) | | [optional] +**operator** | **bool** | Use the CloudNative-PG operator instead of a plain Deployment (postgres only) | [optional] +**instances** | **int** | Number of PostgreSQL instances managed by the CNPG operator (only used when operator is true) | [optional] +**api_server_cidr** | **List[str]** | CIDR(s) allowed for CNPG pods to reach the Kubernetes API server (port 443). Override with your cluster API-server or service CIDR. | [optional] +**initialdb** | **str** | Initial database name (postgres only) | [optional] ## Example diff --git a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withpostgres.yaml b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withpostgres.yaml index 4b260dbf..d808f735 100644 --- a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withpostgres.yaml +++ b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withpostgres.yaml @@ -1,2 +1,6 @@ harness: - database: {auto: true, type: postgres} \ No newline at end of file + database: + auto: true + type: postgres + postgres: + image: postgres:17 \ No newline at end of file