diff --git a/.github/instructions/copilot-instructions.md b/.github/instructions/copilot-instructions.md index b54a16327..5ba106c37 100644 --- a/.github/instructions/copilot-instructions.md +++ b/.github/instructions/copilot-instructions.md @@ -20,4 +20,10 @@ Verify which application/components is in scope and read specific prompt instruc Check best practices in every instruction file in scope and docs and apply them when writing code or performing code reviews. Use reference for any questions regarding project structure, development workflow, and best practices. -If you have any doubts about where to find information, ask for clarification before proceeding. \ No newline at end of file +If you have any doubts about where to find information, ask for clarification before proceeding. + +### Development principles +- Follow the best practices and coding style guidelines outlined in the documentation and instruction files. +- Configuration is set on values.yaml files and injected into the application via Helm templates and Kubernetes manifests. Do not hardcode configuration values directly into the application code or templates. +- Structured configuration can be injected via resources, that are process by helm templates and loaded as ConfigMaps automatically. See for instance `applications/accounts/deploy/resources/realm.json` +- The cloud harness configuration API is handled by the models library and defined as [openapi spec](../../libraries/models/api/openapi.yaml). Use `harness-generate models` to generate the models library after making changes to the spec. \ No newline at end of file diff --git a/applications/samples/api/openapi.yaml b/applications/samples/api/openapi.yaml index f51b328df..ed4adc913 100644 --- a/applications/samples/api/openapi.yaml +++ b/applications/samples/api/openapi.yaml @@ -83,6 +83,22 @@ paths: description: | Check if the token is valid x-openapi-router-controller: samples.controllers.auth_controller + /db-connect-string: + get: + tags: + - database + summary: Get database connection string + operationId: get_db_connect_string + description: Returns the database connection string for the current application. + responses: + "200": + description: Database connection string returned successfully + content: + application/json: + schema: + type: string + "500": + description: Error retrieving database connection string /sampleresources: summary: Path used to manage the list of sampleresources. description: >- diff --git a/applications/samples/backend/requirements.txt b/applications/samples/backend/requirements.txt index 68760177c..2cb06891c 100644 --- a/applications/samples/backend/requirements.txt +++ b/applications/samples/backend/requirements.txt @@ -1,9 +1,13 @@ -connexion[swagger-ui,flask,uvicorn]>=3.0.0,<4.0.0 -swagger-ui-bundle>=1.1.0 -python_dateutil>=2.9.0 -setuptools>=21.0.0 -uvicorn -# Following some unnecessary requirements to make sure they can be installed -psycopg2-binary -sqlalchemy<2.0.0 -scipy \ No newline at end of file +connexion[swagger-ui] >= 2.6.0; python_version>="3.6" +# 2.3 is the last version that supports python 3.4-3.5 +connexion[swagger-ui] <= 2.3.0; python_version=="3.5" or python_version=="3.4" +# prevent breaking dependencies from advent of connexion>=3.0 +connexion[swagger-ui] <= 2.14.2; python_version>"3.4" +# connexion requires werkzeug but connexion < 2.4.0 does not install werkzeug +# we must peg werkzeug versions below to fix connexion +# https://github.com/zalando/connexion/pull/1044 +werkzeug == 0.16.1; python_version=="3.5" or python_version=="3.4" +swagger-ui-bundle >= 0.0.2 +python_dateutil >= 2.6.0 +setuptools >= 21.0.0 +Flask == 2.1.1 diff --git a/applications/samples/backend/samples/controllers/database_controller.py b/applications/samples/backend/samples/controllers/database_controller.py new file mode 100644 index 000000000..33750c745 --- /dev/null +++ b/applications/samples/backend/samples/controllers/database_controller.py @@ -0,0 +1,20 @@ +import connexion +from typing import Dict +from typing import Tuple +from typing import Union + +from samples.models.get_db_connect_string200_response import GetDbConnectString200Response # noqa: E501 +from samples import util + + +def get_db_connect_string(): # noqa: E501 + """Get database connection string + + Returns the database connection string for the current application. # noqa: E501 + + + :rtype: Union[GetDbConnectString200Response, Tuple[GetDbConnectString200Response, int], Tuple[GetDbConnectString200Response, int, Dict[str, str]] + """ + from cloudharness.applications import get_current_configuration + config = get_current_configuration() + return config.get_db_connection_string() diff --git a/applications/samples/backend/samples/controllers/security_controller.py b/applications/samples/backend/samples/controllers/security_controller.py new file mode 100644 index 000000000..25258489b --- /dev/null +++ b/applications/samples/backend/samples/controllers/security_controller.py @@ -0,0 +1,31 @@ +from typing import List + + +def info_from_bearerAuth(token): + """ + Check and retrieve authentication information from custom bearer token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'uid': 'user_id'} + + +def info_from_cookieAuth(api_key, required_scopes): + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param api_key API key provided by Authorization header + :type api_key: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} diff --git a/applications/samples/backend/samples/models/get_db_connect_string200_response.py b/applications/samples/backend/samples/models/get_db_connect_string200_response.py new file mode 100644 index 000000000..9e4cae959 --- /dev/null +++ b/applications/samples/backend/samples/models/get_db_connect_string200_response.py @@ -0,0 +1,61 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from samples.models.base_model import Model +from samples import util + + +class GetDbConnectString200Response(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, connect_string=None): # noqa: E501 + """GetDbConnectString200Response - a model defined in OpenAPI + + :param connect_string: The connect_string of this GetDbConnectString200Response. # noqa: E501 + :type connect_string: str + """ + self.openapi_types = { + 'connect_string': str + } + + self.attribute_map = { + 'connect_string': 'connect_string' + } + + self._connect_string = connect_string + + @classmethod + def from_dict(cls, dikt) -> 'GetDbConnectString200Response': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The get_db_connect_string_200_response of this GetDbConnectString200Response. # noqa: E501 + :rtype: GetDbConnectString200Response + """ + return util.deserialize_model(dikt, cls) + + @property + def connect_string(self) -> str: + """Gets the connect_string of this GetDbConnectString200Response. + + + :return: The connect_string of this GetDbConnectString200Response. + :rtype: str + """ + return self._connect_string + + @connect_string.setter + def connect_string(self, connect_string: str): + """Sets the connect_string of this GetDbConnectString200Response. + + + :param connect_string: The connect_string of this GetDbConnectString200Response. + :type connect_string: str + """ + + self._connect_string = connect_string diff --git a/applications/samples/backend/samples/openapi/openapi.yaml b/applications/samples/backend/samples/openapi/openapi.yaml index 401ba448e..3bf1d1c54 100644 --- a/applications/samples/backend/samples/openapi/openapi.yaml +++ b/applications/samples/backend/samples/openapi/openapi.yaml @@ -15,6 +15,23 @@ tags: - description: "" name: resource paths: + /db-connect-string: + get: + description: Returns the database connection string for the current application. + operationId: get_db_connect_string + responses: + "200": + content: + application/json: + schema: + type: string + description: Database connection string returned successfully + "500": + description: Error retrieving database connection string + summary: Get database connection string + tags: + - database + x-openapi-router-controller: samples.controllers.database_controller /error: get: operationId: error @@ -336,4 +353,4 @@ components: in: cookie name: kc-access type: apiKey - x-apikeyInfoFunc: samples.controllers.security_controller_.info_from_cookieAuth + x-apikeyInfoFunc: cloudharness.auth.decode_token diff --git a/applications/samples/deploy/values.yaml b/applications/samples/deploy/values.yaml index 607c99445..b5601aba9 100644 --- a/applications/samples/deploy/values.yaml +++ b/applications/samples/deploy/values.yaml @@ -17,7 +17,10 @@ harness: size: 10Mi usenfs: false auto: true - port: 8080 + port: 8080 + gateway: + path: / + pathType: Prefix proxy: gatekeeper: replicas: 1 @@ -96,4 +99,7 @@ harness: dockerfile: buildArgs: - TEST_ARGUMENT: example value \ No newline at end of file + TEST_ARGUMENT: example value + database: + type: postgres + connect_string: "test connection string" \ No newline at end of file diff --git a/deployment-configuration/compose/templates/auto-gatekeepers.yaml b/deployment-configuration/compose/templates/auto-gatekeepers.yaml index 730f5bd07..4b78ec865 100644 --- a/deployment-configuration/compose/templates/auto-gatekeepers.yaml +++ b/deployment-configuration/compose/templates/auto-gatekeepers.yaml @@ -6,7 +6,7 @@ networks: - ch restart: always - image: quay.io/gogatekeeper/gatekeeper:2.14.3 + image: quay.io/gogatekeeper/gatekeeper:4.6.0 expose: - '8080' - '8443' diff --git a/deployment-configuration/helm/templates/auto-deployments.yaml b/deployment-configuration/helm/templates/auto-deployments.yaml index 874ec99de..efa125d56 100644 --- a/deployment-configuration/helm/templates/auto-deployments.yaml +++ b/deployment-configuration/helm/templates/auto-deployments.yaml @@ -140,6 +140,13 @@ spec: mountPath: "/opt/cloudharness/resources/secrets/{{ .app.harness.name }}" readOnly: true {{- end }} + {{- if kindIs "map" .app.harness.database }} + {{- if and (hasKey .app.harness.database "connect_string") .app.harness.database.connect_string }} + - name: db-external + mountPath: "/opt/cloudharness/resources/db" + readOnly: true + {{- end }} + {{- end }} volumes: - name: cloudharness-allvalues configMap: @@ -169,6 +176,13 @@ spec: secret: secretName: {{ .app.harness.deployment.name }} {{- end }} + {{- if kindIs "map" .app.harness.database }} + {{- if and (hasKey .app.harness.database "connect_string") .app.harness.database.connect_string }} + - name: db-external + secret: + secretName: {{ printf "%s-db" .app.harness.deployment.name }} + {{- end }} + {{- end }} --- {{- end }} diff --git a/deployment-configuration/helm/templates/auto-gatekeepers.yaml b/deployment-configuration/helm/templates/auto-gatekeepers.yaml index 2e1e6197a..1ceadedba 100644 --- a/deployment-configuration/helm/templates/auto-gatekeepers.yaml +++ b/deployment-configuration/helm/templates/auto-gatekeepers.yaml @@ -27,6 +27,7 @@ data: forbidden-page: /templates/access-denied.html.tmpl enable-default-deny: {{ $noWildcards }} listen: 0.0.0.0:8080 + enable-encrypted-token: false enable-refresh-tokens: true server-write-timeout: {{ .app.harness.proxy.timeout.send | default .root.Values.proxy.timeout.send | default 180 }}s upstream-timeout: {{ .app.harness.proxy.timeout.read | default .root.Values.proxy.timeout.read | default 180 }}s @@ -38,7 +39,6 @@ data: tls-cert: tls-private-key: redirection-url: {{ ternary "https" "http" $tls }}://{{ .subdomain }}.{{ .root.Values.domain }} - encryption-key: AgXa7xRcoClDEU0ZDSH4X0XhL5Qy2Z2j upstream-url: http://{{ .app.harness.service.name }}.{{ .app.namespace | default .root.Release.Namespace }}:{{ .app.harness.service.port | default 80}} {{ if .app.harness.secured }} {{ with .app.harness.uri_role_mapping }} @@ -100,6 +100,34 @@ data: --- +{{- $gkSecretName := printf "%s-gk" .subdomain }} +{{- $existingGkSecret := (lookup "v1" "Secret" .root.Values.namespace $gkSecretName) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $gkSecretName }} + namespace: {{ .root.Values.namespace }} + labels: + app: {{ $gkSecretName }} +type: Opaque +stringData: + updated: {{ now | quote }} + {{- if .root.Values.proxy.gatekeeper.secret }} + encryption-key: {{ .root.Values.proxy.gatekeeper.secret | quote }} + {{- else }} + {{- $hasExisting := false }} + {{- if $existingGkSecret }} + {{- if eq (typeOf $existingGkSecret.data) (typeOf dict) }} + {{- if hasKey $existingGkSecret.data "encryption-key" }} + {{- $hasExisting = true }} + {{- end }} + {{- end }} + {{- end }} + {{- if not $hasExisting }} + encryption-key: {{ randAlphaNum 32 | quote }} + {{- end }} + {{- end }} +--- apiVersion: v1 kind: Service metadata: @@ -135,7 +163,7 @@ spec: {{ include "deploy_utils.etcHosts" .root | indent 6 }} containers: - name: {{ .app.harness.service.name | quote }} - image: {{ .app.harness.proxy.gatekeeper.image | default .root.Values.proxy.gatekeeper.image | default "quay.io/gogatekeeper/gatekeeper:2.14.3" }} + image: {{ .app.harness.proxy.gatekeeper.image | default .root.Values.proxy.gatekeeper.image | default "quay.io/gogatekeeper/gatekeeper:4.6.0" }} imagePullPolicy: IfNotPresent {{ if .root.Values.local }} securityContext: @@ -147,6 +175,11 @@ spec: value: /opt/proxy.yml - name: PROXY_ENABLE_METRICS value: "true" + - name: PROXY_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: "{{ .subdomain }}-gk" + key: encryption-key volumeMounts: - name: "{{ .subdomain }}-gk-proxy-config" mountPath: /opt/proxy.yml diff --git a/deployment-configuration/helm/templates/auto-secrets.yaml b/deployment-configuration/helm/templates/auto-secrets.yaml index 0a1ccc3b0..ad17d1e68 100644 --- a/deployment-configuration/helm/templates/auto-secrets.yaml +++ b/deployment-configuration/helm/templates/auto-secrets.yaml @@ -57,4 +57,25 @@ stringData: {{- end }}{{- end }}{{- end }} {{- end }} {{- end }} +{{- end }} +{{- define "deploy_utils.db_secret" }} +{{- $secret_name := printf "%s-db" .app.harness.deployment.name }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secret_name }} + namespace: {{ .root.Values.namespace }} + labels: + app: {{ .app.harness.deployment.name }} +type: Opaque +stringData: + connect_string: {{ .app.harness.database.connect_string | quote }} +--- +{{- end }} +{{- range $app := .Values.apps }} + {{- if kindIs "map" $app.harness.database }} + {{- if and (hasKey $app.harness.database "connect_string") $app.harness.database.connect_string }} + {{- include "deploy_utils.db_secret" (dict "root" $ "app" $app) }} + {{- end }} + {{- end }} {{- end }} \ No newline at end of file diff --git a/deployment-configuration/helm/templates/certs/letsencrypt.yaml b/deployment-configuration/helm/templates/certs/letsencrypt.yaml index be45ac346..7135e79c0 100644 --- a/deployment-configuration/helm/templates/certs/letsencrypt.yaml +++ b/deployment-configuration/helm/templates/certs/letsencrypt.yaml @@ -12,5 +12,5 @@ spec: solvers: - http01: ingress: - class: nginx + class: {{ .Values.ingress.ingressClass }} {{ end }} diff --git a/deployment-configuration/helm/templates/configmap.yaml b/deployment-configuration/helm/templates/configmap.yaml index ae5265530..d061a9354 100644 --- a/deployment-configuration/helm/templates/configmap.yaml +++ b/deployment-configuration/helm/templates/configmap.yaml @@ -20,5 +20,8 @@ data: {{- range $key, $val := .Values.apps }} {{- $app := get $values_copy.apps $key }} {{- $tmp := set $app.harness "secrets" dict }} + {{- if kindIs "map" $app.harness.database }} + {{- $tmp := unset $app.harness.database "connect_string" }} + {{- end }} {{- end }} {{ $values_copy | toYaml | indent 4 }} \ No newline at end of file diff --git a/deployment-configuration/helm/templates/ingress.yaml b/deployment-configuration/helm/templates/ingress.yaml index d13a3a7a1..0416cfd38 100644 --- a/deployment-configuration/helm/templates/ingress.yaml +++ b/deployment-configuration/helm/templates/ingress.yaml @@ -1,58 +1,60 @@ {{- define "deploy_utils.ingress.http" }} + {{ $root := .root }} {{ $domain := .root.Values.domain }} {{ $secured_gatekeepers := and .root.Values.secured_gatekeepers }} {{ $app := .app }} + {{ $subdomain := .subdomain }} http: paths: -{{- if and $app.harness.secured $secured_gatekeepers $app.harness.uri_role_mapping }} - {{- range $mapping := $app.harness.uri_role_mapping }} - {{- if and (hasKey $mapping "white-listed") (index $mapping "white-listed") }} - {{- $uri := $mapping.uri }} - {{- if eq $uri "/" }} - - path: /() - pathType: ImplementationSpecific + {{- if and $app.harness.secured $secured_gatekeepers $app.harness.uri_role_mapping (eq $app.harness.gateway.pathType "Prefix") }} + {{- range $mapping := $app.harness.uri_role_mapping }} + {{- if and (hasKey $mapping "white-listed") (index $mapping "white-listed") }} + {{- $uri := $mapping.uri }} + {{- if eq $uri "/" }} + - path: / + pathType: Exact backend: service: name: {{ $app.harness.service.name | quote }} port: number: {{ $app.harness.service.port | default 80 }} - {{- else if hasSuffix "/*" $uri }} - {{- $cleanPath := trimSuffix "/*" $uri }} - {{- $pathWithoutSlash := trimPrefix "/" $cleanPath }} - - path: {{ printf "/(%s/.*)" $pathWithoutSlash }} - pathType: ImplementationSpecific + {{- else if hasSuffix "/*" $uri }} + {{- $cleanPath := trimSuffix "/*" $uri }} + {{- $pathWithoutSlash := trimPrefix "/" $cleanPath }} + - path: {{ printf "%s%s" $app.harness.gateway.path $pathWithoutSlash }} + pathType: Prefix backend: service: name: {{ $app.harness.service.name | quote }} port: number: {{ $app.harness.service.port | default 80 }} - {{- else if not (contains "*" $uri) }} - {{- $pathWithoutSlash := trimPrefix "/" $uri }} - - path: {{ printf "/(%s)" $pathWithoutSlash }} - pathType: ImplementationSpecific + {{- else if (not (contains "*" $uri)) }} + {{- $pathWithoutSlash := trimPrefix "/" $uri }} + - path: {{ printf "%s%s" $app.harness.gateway.path $pathWithoutSlash }} + pathType: Exact backend: service: name: {{ $app.harness.service.name | quote }} port: number: {{ $app.harness.service.port | default 80 }} + {{- end }} + {{- end }} + {{- end }} {{- end }} - {{- end }} - {{- end }} -{{- end }} - - path: /(.*) - pathType: ImplementationSpecific + - path: {{ $app.harness.gateway.path | default $root.Values.ingress.path | default "/" }} + pathType: {{ $app.harness.gateway.pathType | default $root.Values.ingress.pathType | default "Prefix" }} backend: service: - name: {{ if (and $app.harness.secured $secured_gatekeepers) }}{{ printf "%s-gk" .subdomain }}{{ else }}{{ $app.harness.service.name | quote }}{{ end }} + name: {{ if (and $app.harness.secured $secured_gatekeepers) }}{{ printf "%s-gk" $subdomain }}{{ else }}{{ $app.harness.service.name | quote }}{{ end }} port: - number: {{- if (and $app.harness.secured $secured_gatekeepers) }} 8080 {{- else }} {{ $app.harness.service.port | default 80 }}{{- end }} - + number: {{- if (and $app.harness.secured $secured_gatekeepers) }} 8080 {{- else }} {{ $app.harness.service.port | default 80 }}{{- end }} {{- end }} {{- define "deploy_utils.ingress.service" }} {{ $domain := .root.Values.domain }} {{ $secured_gatekeepers := and .root.Values.secured_gatekeepers }} {{ $app := get .root.Values.apps .service_name}} - - path: /proxy/{{ $app.harness.service.name }}/(.*) + {{ $routePattern := default .root.Values.ingress.path $app.harness.gateway.path }} + - path: /proxy/{{ $app.harness.service.name }}/{{- if eq .root.Values.ingress.ingressClass "nginx" }}(.*){{- else }}.*{{- end }} pathType: ImplementationSpecific backend: service: @@ -61,92 +63,155 @@ number: {{- if (and $app.harness.secured $secured_gatekeepers) }} 8080 {{- else }} {{ $app.harness.service.port | default 80 }}{{- end }} {{- end }} {{- if .Values.ingress.enabled }} -{{ $domain := .Values.domain }} -{{ $tls := not (not .Values.tls) }} -{{ $mainapp := .Values.mainapp }} + {{- $domain := .Values.domain }} + {{- $tls := not (not .Values.tls) }} + {{- $mainapp := .Values.mainapp }} + {{- range $app := .Values.apps }} + {{- if or $app.harness.subdomain $app.harness.domain $app.harness.aliases (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} + {{- $appIngressName := default $app.harness.name $app.harness.service.name }} +--- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ .Values.ingress.name | quote }} + name: {{ $appIngressName | quote }} annotations: - kubernetes.io/ingress.class: nginx # Deprecated by Kubernetes, however still required for GKE - {{- if and (not .Values.local) $tls }} + kubernetes.io/ingress.class: {{ $.Values.ingress.ingressClass }} # Deprecated by Kubernetes, however still required for GKE + {{- if and (not $.Values.local) $tls }} kubernetes.io/tls-acme: 'true' - cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" .Values.namespace }} + cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" $.Values.namespace }} {{- end }} - nginx.ingress.kubernetes.io/ssl-redirect: {{ (and $tls .Values.ingress.ssl_redirect) | quote }} - nginx.ingress.kubernetes.io/proxy-body-size: '{{ .Values.proxy.payload.max }}m' + {{- if eq $.Values.ingress.ingressClass "nginx" }} + nginx.ingress.kubernetes.io/ssl-redirect: {{ (and $tls $.Values.ingress.ssl_redirect) | quote }} + nginx.ingress.kubernetes.io/proxy-body-size: '{{ $.Values.proxy.payload.max }}m' nginx.ingress.kubernetes.io/proxy-buffer-size: '128k' nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-buffers-number: "8" nginx.ingress.kubernetes.io/proxy-max-temp-file-size: "1024m" nginx.ingress.kubernetes.io/from-to-www-redirect: 'true' - nginx.ingress.kubernetes.io/rewrite-target: /$1 - nginx.ingress.kubernetes.io/auth-keepalive-timeout: {{ .Values.proxy.timeout.keepalive | quote }} - nginx.ingress.kubernetes.io/proxy-read-timeout: {{ .Values.proxy.timeout.read | quote }} - nginx.ingress.kubernetes.io/proxy-send-timeout: {{ .Values.proxy.timeout.send | quote }} - nginx.ingress.kubernetes.io/use-forwarded-headers: {{ .Values.proxy.forwardedHeaders | quote }} + nginx.ingress.kubernetes.io/auth-keepalive-timeout: {{ $.Values.proxy.timeout.keepalive | quote }} + nginx.ingress.kubernetes.io/proxy-read-timeout: {{ $.Values.proxy.timeout.read | quote }} + nginx.ingress.kubernetes.io/proxy-send-timeout: {{ $.Values.proxy.timeout.send | quote }} + nginx.ingress.kubernetes.io/use-forwarded-headers: {{ $.Values.proxy.forwardedHeaders | quote }} + {{- else if eq $.Values.ingress.ingressClass "traefik" }} + traefik.ingress.kubernetes.io/router.middlewares: {{ printf "%s-ch-body-size@kubernetescrd" $.Values.namespace }}{{ if and $tls $.Values.ingress.ssl_redirect }},{{ printf "%s-ch-ssl-redirect@kubernetescrd" $.Values.namespace }}{{ end }} + {{- end }} + {{- with $.Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: - ingressClassName: nginx + ingressClassName: {{ $.Values.ingress.ingressClass }} rules: - {{- range $app := .Values.apps }} - {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} - {{- if ne $app.harness.subdomain "www" }} + {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} + {{- if ne $app.harness.subdomain "www" }} - host: {{ $domain | quote }} {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $app.harness.subdomain) }} - {{- range $service := $app.harness.use_services }} - {{ include "deploy_utils.ingress.service" (dict "root" $ "service_name" $service.name) }} - {{- end }} + {{- end }} {{- end }} - {{- end }} - {{- if $app.harness.domain }} + {{- if $app.harness.domain }} - host: {{ $app.harness.domain | quote }} - {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $app.harness.subdomain ) }} - {{- end }} - {{- if $app.harness.aliases }} - {{- range $alias := $app.harness.aliases }} + {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $app.harness.subdomain) }} + {{- end }} + {{- if $app.harness.aliases }} + {{- range $alias := $app.harness.aliases }} - host: {{ printf "%s.%s" $alias $domain | quote }} - {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $alias) }} + {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $alias) }} + {{- end }} {{- end }} - {{- end }} - {{- if $app.harness.subdomain }} + {{- if $app.harness.subdomain }} - host: {{ printf "%s.%s" $app.harness.subdomain $domain | quote }} - {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $app.harness.subdomain) }} - {{- range $service := $app.harness.use_services }} - {{ include "deploy_utils.ingress.service" (dict "root" $ "service_name" $service.name) }} - {{- end }} - {{- range $subapp := $app }} - {{- if contains "map" (typeOf $subapp) }} - {{- if and $subapp (hasKey $subapp "harness.subdomain") }} + {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $app "subdomain" $app.harness.subdomain) }} + {{- range $subapp := $app }} + {{- if contains "map" (typeOf $subapp) }} + {{- if and $subapp (hasKey $subapp "harness.subdomain") }} - host: {{ printf "%s.%s.%s" $subapp.harness.subdomain $app.harness.subdomain $domain | quote }} {{ include "deploy_utils.ingress.http" (dict "root" $ "app" $subapp "subdomain" (printf "%s.%s" $subapp.harness.subdomain $app.harness.subdomain)) }} + {{- end }} {{- end }} - {{- end }} + {{- end }} {{- end }} - {{- end }} - - {{- end }} - {{- if $tls }} + {{- if $tls }} tls: - hosts: - {{- range $app := .Values.apps }} {{- if $app.harness.subdomain }} - {{ printf "%s.%s" $app.harness.subdomain $domain | quote }} {{- end }} - {{- if $app.harness.domain }} - {{- if ne $app.harness.domain $domain }} + {{- if $app.harness.domain }} + {{- if ne $app.harness.domain $domain }} - {{ $app.harness.domain | quote }} - {{- end }} - {{- end }} - {{- if $app.harness.aliases }} - {{- range $subdomain := $app.harness.aliases }} + {{- end }} + {{- end }} + {{- if $app.harness.aliases }} + {{- range $subdomain := $app.harness.aliases }} - {{ printf "%s.%s" $subdomain $domain | quote }} + {{- end }} {{- end }} - {{- end }} + {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} + {{- if ne $app.harness.subdomain "www" }} + - {{ $domain | quote }} + {{- end }} + {{- end }} + secretName: {{ printf "tls-secret-%s" $appIngressName }} {{- end }} - {{- if $mainapp }} + {{- if $app.harness.use_services }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ printf "%s-proxy" $appIngressName | quote }} + annotations: + kubernetes.io/ingress.class: {{ $.Values.ingress.ingressClass }} # Deprecated by Kubernetes, however still required for GKE + {{- if and (not $.Values.local) $tls }} + kubernetes.io/tls-acme: 'true' + cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" $.Values.namespace }} + {{- end }} + {{- if eq $.Values.ingress.ingressClass "nginx" }} + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/proxy-body-size: '{{ $.Values.proxy.payload.max }}m' + nginx.ingress.kubernetes.io/proxy-read-timeout: {{ $.Values.proxy.timeout.read | quote }} + nginx.ingress.kubernetes.io/proxy-send-timeout: {{ $.Values.proxy.timeout.send | quote }} + nginx.ingress.kubernetes.io/use-forwarded-headers: {{ $.Values.proxy.forwardedHeaders | quote }} + {{- else if eq $.Values.ingress.ingressClass "traefik" }} + traefik.ingress.kubernetes.io/router.pathmatcher: "PathRegexp" + traefik.ingress.kubernetes.io/router.middlewares: {{ printf "%s-ch-proxy-rewrite@kubernetescrd,%s-ch-body-size@kubernetescrd" $.Values.namespace $.Values.namespace }}{{ if and $tls $.Values.ingress.ssl_redirect }},{{ printf "%s-ch-ssl-redirect@kubernetescrd" $.Values.namespace }}{{ end }} + {{- end }} + {{- with $.Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ $.Values.ingress.ingressClass }} + rules: + {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} + {{- if ne $app.harness.subdomain "www" }} + - host: {{ $domain | quote }} + http: + paths: + {{- range $service := $app.harness.use_services }} + {{ include "deploy_utils.ingress.service" (dict "root" $ "service_name" $service.name) }} + {{- end }} + {{- end }} + {{- end }} + {{- if $app.harness.subdomain }} + - host: {{ printf "%s.%s" $app.harness.subdomain $domain | quote }} + http: + paths: + {{- range $service := $app.harness.use_services }} + {{ include "deploy_utils.ingress.service" (dict "root" $ "service_name" $service.name) }} + {{- end }} + {{- end }} + {{- if $tls }} + tls: + - hosts: + {{- if $app.harness.subdomain }} + - {{ printf "%s.%s" $app.harness.subdomain $domain | quote }} + {{- end }} + {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} + {{- if ne $app.harness.subdomain "www" }} - {{ $domain | quote }} + {{- end }} + {{- end }} + secretName: {{ printf "tls-secret-%s" $appIngressName }} + {{- end }} {{- end }} - secretName: tls-secret + {{- end }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/deployment-configuration/helm/templates/tls-secret.yaml b/deployment-configuration/helm/templates/tls-secret.yaml index ac871d2f4..2979c988b 100644 --- a/deployment-configuration/helm/templates/tls-secret.yaml +++ b/deployment-configuration/helm/templates/tls-secret.yaml @@ -1,13 +1,17 @@ -{{ if and .Values.local .Values.tls }} +{{- if and .Values.local .Values.tls }} +{{- range $app := .Values.apps }} + {{- if or $app.harness.subdomain $app.harness.domain $app.harness.aliases }} + {{- $appIngressName := default $app.harness.name $app.harness.service.name }} apiVersion: v1 kind: Secret metadata: - name: tls-secret + name: {{ printf "tls-secret-%s" $appIngressName }} type: kubernetes.io/tls data: - tls.crt: {{ .Files.Get "resources/certs/tls.crt" | b64enc | quote }} - tls.key: {{ .Files.Get "resources/certs/tls.key" | b64enc | quote }} + tls.crt: {{ $.Files.Get "resources/certs/tls.crt" | b64enc | quote }} + tls.key: {{ $.Files.Get "resources/certs/tls.key" | b64enc | quote }} --- + {{- end }} +{{- end }} {{- end }} - diff --git a/deployment-configuration/helm/templates/traefik-middlewares.yaml b/deployment-configuration/helm/templates/traefik-middlewares.yaml new file mode 100644 index 000000000..b3349384b --- /dev/null +++ b/deployment-configuration/helm/templates/traefik-middlewares.yaml @@ -0,0 +1,45 @@ +{{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik") }} +{{ $tls := not (not .Values.tls) }} +# Rewrite target: equivalent of nginx.ingress.kubernetes.io/rewrite-target: /$1 +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-rewrite-target +spec: + replacePathRegex: + regex: "^/[^/]+(.*)" + replacement: "/$1" +--- +# Proxy rewrite: strips /proxy/ prefix for use_services proxy routes +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-proxy-rewrite +spec: + replacePathRegex: + regex: "^/proxy/[^/]+(.*)" + replacement: "$1" +--- +# Body size / buffering: equivalent of nginx proxy-body-size, proxy-buffer-size +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-body-size +spec: + buffering: + maxRequestBodyBytes: {{ mul .Values.proxy.payload.max 1048576 }} + memRequestBodyBytes: 131072 + memResponseBodyBytes: 131072 +{{- if and $tls .Values.ingress.ssl_redirect }} +--- +# SSL redirect: equivalent of nginx.ingress.kubernetes.io/ssl-redirect +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-ssl-redirect +spec: + redirectScheme: + scheme: https + permanent: true +{{- end }} +{{- end }} diff --git a/deployment-configuration/helm/values.yaml b/deployment-configuration/helm/values.yaml index 8b83c0477..fe7522263 100644 --- a/deployment-configuration/helm/values.yaml +++ b/deployment-configuration/helm/values.yaml @@ -35,11 +35,19 @@ ingress: enabled: true # -- K8s Name of ingress. name: cloudharness-ingress + # -- The ingress class to use (e.g. "nginx", "traefik"). + ingressClass: nginx + # -- Additional custom annotations to add to the Ingress resource. + annotations: {} # -- Enables/disables SSL redirect. ssl_redirect: true letsencrypt: # -- Email for letsencrypt. email: cloudharness@metacell.us + # -- Default regex segment for routes (used in paths like '/(pattern)'). + path: "/" + # -- The pathType for the Ingress path. Default is Prefix. For regex paths, set to ImplementationSpecific + pathType: Prefix backup: # -- Flag to enable/disable backups. active: false @@ -82,11 +90,12 @@ proxy: max: 250 gatekeeper: # -- Default gatekeeper image - image: "quay.io/gogatekeeper/gatekeeper:2.14.3" + image: "quay.io/gogatekeeper/gatekeeper:4.6.0" # -- Default number of gatekeeper replicas replicas: 1 + secret: "" resources: requests: memory: "32Mi" limits: - memory: "64Mi" \ No newline at end of file + memory: "64Mi" diff --git a/deployment-configuration/value-template.yaml b/deployment-configuration/value-template.yaml index f02e03e53..cb1f964e2 100644 --- a/deployment-configuration/value-template.yaml +++ b/deployment-configuration/value-template.yaml @@ -77,6 +77,8 @@ harness: image_ref: # -- expose database to the public with ingress expose: false + # -- Set to "" to set the set the string in the CI/CD as a secret. Only set the full value for dev/testing + connect_string: # -- settings for mongo database (for type==mongo) mongo: image: mongo:5 @@ -140,11 +142,16 @@ harness: max: gatekeeper: # -- Default gatekeeper image - image: "quay.io/gogatekeeper/gatekeeper:2.14.3" + image: "quay.io/gogatekeeper/gatekeeper:4.6.0" # -- Default number of gatekeeper replicas replicas: 1 resources: requests: memory: "32Mi" limits: - memory: "64Mi" \ No newline at end of file + memory: "64Mi" + gateway: + ## -- The path mapped to the current application in the gateway. Default is "/". For regex paths, use the path specified in .Values.ingress.path and set pathType to ImplementationSpecific + path: "/" + ## -- The pathType for the Ingress path. Default is Prefix. For regex paths, set to ImplementationSpecific + pathType: Prefix \ No newline at end of file diff --git a/deployment/codefresh-test.yaml b/deployment/codefresh-test.yaml index 95d35e59c..0012e877b 100644 --- a/deployment/codefresh-test.yaml +++ b/deployment/codefresh-test.yaml @@ -47,7 +47,7 @@ steps: type: parallel stage: build steps: - test-e2e: + accounts: type: build stage: build dockerfile: Dockerfile @@ -55,20 +55,41 @@ steps: buildkit: true build_arguments: - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/test-e2e - title: Test e2e - working_directory: ./test/test-e2e + image_name: cloud-harness/accounts + title: Accounts + working_directory: ./applications/accounts tags: - - '${{TEST_E2E_TAG}}' + - '${{ACCOUNTS_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - - latest when: condition: any: - buildDoesNotExist: includes('${{TEST_E2E_TAG_EXISTS}}', '{{TEST_E2E_TAG_EXISTS}}') + buildDoesNotExist: includes('${{ACCOUNTS_TAG_EXISTS}}', '{{ACCOUNTS_TAG_EXISTS}}') == true - forceNoCache: includes('${{TEST_E2E_TAG_FORCE_BUILD}}', '{{TEST_E2E_TAG_FORCE_BUILD}}') + forceNoCache: includes('${{ACCOUNTS_TAG_FORCE_BUILD}}', '{{ACCOUNTS_TAG_FORCE_BUILD}}') + == false + cloudharness-base: + type: build + stage: build + dockerfile: infrastructure/base-images/cloudharness-base/Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + image_name: cloud-harness/cloudharness-base + title: Cloudharness base + working_directory: ./. + tags: + - '${{CLOUDHARNESS_BASE_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{CLOUDHARNESS_BASE_TAG_EXISTS}}', '{{CLOUDHARNESS_BASE_TAG_EXISTS}}') + == true + forceNoCache: includes('${{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}') == false cloudharness-frontend-build: type: build @@ -92,7 +113,7 @@ steps: '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_EXISTS}}') == true forceNoCache: includes('${{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}') == false - accounts: + test-e2e: type: build stage: build dockerfile: Dockerfile @@ -100,48 +121,50 @@ steps: buildkit: true build_arguments: - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/accounts - title: Accounts - working_directory: ./applications/accounts + image_name: cloud-harness/test-e2e + title: Test e2e + working_directory: ./test/test-e2e tags: - - '${{ACCOUNTS_TAG}}' + - '${{TEST_E2E_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + - latest when: condition: any: - buildDoesNotExist: includes('${{ACCOUNTS_TAG_EXISTS}}', '{{ACCOUNTS_TAG_EXISTS}}') + buildDoesNotExist: includes('${{TEST_E2E_TAG_EXISTS}}', '{{TEST_E2E_TAG_EXISTS}}') == true - forceNoCache: includes('${{ACCOUNTS_TAG_FORCE_BUILD}}', '{{ACCOUNTS_TAG_FORCE_BUILD}}') + forceNoCache: includes('${{TEST_E2E_TAG_FORCE_BUILD}}', '{{TEST_E2E_TAG_FORCE_BUILD}}') == false - cloudharness-base: + title: Build parallel step 1 + build_application_images_1: + type: parallel + stage: build + steps: + cloudharness-django: type: build stage: build - dockerfile: infrastructure/base-images/cloudharness-base/Dockerfile + dockerfile: Dockerfile registry: '${{CODEFRESH_REGISTRY}}' buildkit: true build_arguments: - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/cloudharness-base - title: Cloudharness base - working_directory: ./. + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/cloudharness-django + title: Cloudharness django + working_directory: ./infrastructure/common-images/cloudharness-django tags: - - '${{CLOUDHARNESS_BASE_TAG}}' + - '${{CLOUDHARNESS_DJANGO_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{CLOUDHARNESS_BASE_TAG_EXISTS}}', '{{CLOUDHARNESS_BASE_TAG_EXISTS}}') + buildDoesNotExist: includes('${{CLOUDHARNESS_DJANGO_TAG_EXISTS}}', '{{CLOUDHARNESS_DJANGO_TAG_EXISTS}}') == true - forceNoCache: includes('${{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}') + forceNoCache: includes('${{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}') == false - title: Build parallel step 1 - build_application_images_1: - type: parallel - stage: build - steps: - samples-print-file: + cloudharness-flask: type: build stage: build dockerfile: Dockerfile @@ -150,21 +173,21 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/sampleapp-print-file - title: Samples print file - working_directory: ./applications/samples/tasks/print-file + image_name: cloud-harness/cloudharness-flask + title: Cloudharness flask + working_directory: ./infrastructure/common-images/cloudharness-flask tags: - - '${{SAMPLES_PRINT_FILE_TAG}}' + - '${{CLOUDHARNESS_FLASK_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{SAMPLES_PRINT_FILE_TAG_EXISTS}}', '{{SAMPLES_PRINT_FILE_TAG_EXISTS}}') + buildDoesNotExist: includes('${{CLOUDHARNESS_FLASK_TAG_EXISTS}}', '{{CLOUDHARNESS_FLASK_TAG_EXISTS}}') == true - forceNoCache: includes('${{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}', '{{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}') + forceNoCache: includes('${{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}') == false - workflows-notify-queue: + jupyterhub: type: build stage: build dockerfile: Dockerfile @@ -173,21 +196,21 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/workflows-notify-queue - title: Workflows notify queue - working_directory: ./applications/workflows/tasks/notify-queue + image_name: cloud-harness/jupyterhub + title: Jupyterhub + working_directory: ./applications/jupyterhub tags: - - '${{WORKFLOWS_NOTIFY_QUEUE_TAG}}' + - '${{JUPYTERHUB_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}', - '{{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}') == true - forceNoCache: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}', - '{{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}') == false - workflows-send-result-event: + buildDoesNotExist: includes('${{JUPYTERHUB_TAG_EXISTS}}', '{{JUPYTERHUB_TAG_EXISTS}}') + == true + forceNoCache: includes('${{JUPYTERHUB_TAG_FORCE_BUILD}}', '{{JUPYTERHUB_TAG_FORCE_BUILD}}') + == false + samples-print-file: type: build stage: build dockerfile: Dockerfile @@ -196,21 +219,21 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/workflows-send-result-event - title: Workflows send result event - working_directory: ./applications/workflows/tasks/send-result-event + image_name: cloud-harness/sampleapp-print-file + title: Samples print file + working_directory: ./applications/samples/tasks/print-file tags: - - '${{WORKFLOWS_SEND_RESULT_EVENT_TAG}}' + - '${{SAMPLES_PRINT_FILE_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}', - '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}') == true - forceNoCache: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}', - '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}') == false - cloudharness-flask: + buildDoesNotExist: includes('${{SAMPLES_PRINT_FILE_TAG_EXISTS}}', '{{SAMPLES_PRINT_FILE_TAG_EXISTS}}') + == true + forceNoCache: includes('${{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}', '{{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}') + == false + samples-secret: type: build stage: build dockerfile: Dockerfile @@ -219,19 +242,19 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/cloudharness-flask - title: Cloudharness flask - working_directory: ./infrastructure/common-images/cloudharness-flask + image_name: cloud-harness/sampleapp-secret + title: Samples secret + working_directory: ./applications/samples/tasks/secret tags: - - '${{CLOUDHARNESS_FLASK_TAG}}' + - '${{SAMPLES_SECRET_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{CLOUDHARNESS_FLASK_TAG_EXISTS}}', '{{CLOUDHARNESS_FLASK_TAG_EXISTS}}') + buildDoesNotExist: includes('${{SAMPLES_SECRET_TAG_EXISTS}}', '{{SAMPLES_SECRET_TAG_EXISTS}}') == true - forceNoCache: includes('${{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}') + forceNoCache: includes('${{SAMPLES_SECRET_TAG_FORCE_BUILD}}', '{{SAMPLES_SECRET_TAG_FORCE_BUILD}}') == false test-api: type: build @@ -257,29 +280,6 @@ steps: == true forceNoCache: includes('${{TEST_API_TAG_FORCE_BUILD}}', '{{TEST_API_TAG_FORCE_BUILD}}') == false - cloudharness-django: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/cloudharness-django - title: Cloudharness django - working_directory: ./infrastructure/common-images/cloudharness-django - tags: - - '${{CLOUDHARNESS_DJANGO_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{CLOUDHARNESS_DJANGO_TAG_EXISTS}}', '{{CLOUDHARNESS_DJANGO_TAG_EXISTS}}') - == true - forceNoCache: includes('${{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}') - == false workflows-extract-download: type: build stage: build @@ -303,7 +303,7 @@ steps: '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_EXISTS}}') == true forceNoCache: includes('${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}', '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}') == false - jupyterhub: + workflows-notify-queue: type: build stage: build dockerfile: Dockerfile @@ -312,21 +312,21 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/jupyterhub - title: Jupyterhub - working_directory: ./applications/jupyterhub + image_name: cloud-harness/workflows-notify-queue + title: Workflows notify queue + working_directory: ./applications/workflows/tasks/notify-queue tags: - - '${{JUPYTERHUB_TAG}}' + - '${{WORKFLOWS_NOTIFY_QUEUE_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{JUPYTERHUB_TAG_EXISTS}}', '{{JUPYTERHUB_TAG_EXISTS}}') - == true - forceNoCache: includes('${{JUPYTERHUB_TAG_FORCE_BUILD}}', '{{JUPYTERHUB_TAG_FORCE_BUILD}}') - == false - samples-secret: + buildDoesNotExist: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}', + '{{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}') == true + forceNoCache: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}', + '{{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}') == false + workflows-send-result-event: type: build stage: build dockerfile: Dockerfile @@ -335,48 +335,25 @@ steps: build_arguments: - NOCACHE=${{CF_BUILD_ID}} - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/sampleapp-secret - title: Samples secret - working_directory: ./applications/samples/tasks/secret + image_name: cloud-harness/workflows-send-result-event + title: Workflows send result event + working_directory: ./applications/workflows/tasks/send-result-event tags: - - '${{SAMPLES_SECRET_TAG}}' + - '${{WORKFLOWS_SEND_RESULT_EVENT_TAG}}' - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' when: condition: any: - buildDoesNotExist: includes('${{SAMPLES_SECRET_TAG_EXISTS}}', '{{SAMPLES_SECRET_TAG_EXISTS}}') - == true - forceNoCache: includes('${{SAMPLES_SECRET_TAG_FORCE_BUILD}}', '{{SAMPLES_SECRET_TAG_FORCE_BUILD}}') - == false + buildDoesNotExist: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}', + '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}') == true + forceNoCache: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}', + '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}') == false title: Build parallel step 2 build_application_images_2: type: parallel stage: build steps: - workflows: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} - image_name: cloud-harness/workflows - title: Workflows - working_directory: ./applications/workflows/server - tags: - - '${{WORKFLOWS_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{WORKFLOWS_TAG_EXISTS}}', '{{WORKFLOWS_TAG_EXISTS}}') - == true - forceNoCache: includes('${{WORKFLOWS_TAG_FORCE_BUILD}}', '{{WORKFLOWS_TAG_FORCE_BUILD}}') - == false common: type: build stage: build @@ -447,6 +424,29 @@ steps: == true forceNoCache: includes('${{VOLUMEMANAGER_TAG_FORCE_BUILD}}', '{{VOLUMEMANAGER_TAG_FORCE_BUILD}}') == false + workflows: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} + image_name: cloud-harness/workflows + title: Workflows + working_directory: ./applications/workflows/server + tags: + - '${{WORKFLOWS_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{WORKFLOWS_TAG_EXISTS}}', '{{WORKFLOWS_TAG_EXISTS}}') + == true + forceNoCache: includes('${{WORKFLOWS_TAG_FORCE_BUILD}}', '{{WORKFLOWS_TAG_FORCE_BUILD}}') + == false title: Build parallel step 3 build_application_images_3: type: parallel @@ -509,13 +509,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/common + - kubectl rollout status deployment/volumemanager + - kubectl rollout status deployment/argo-gk - kubectl rollout status deployment/samples - kubectl rollout status deployment/samples-gk - - kubectl rollout status deployment/argo-gk - - kubectl rollout status deployment/volumemanager - kubectl rollout status deployment/accounts + - kubectl rollout status deployment/common + - kubectl rollout status deployment/workflows - sleep 60 tests_api: stage: qa diff --git a/docs/ingress-domains-proxies.md b/docs/ingress-domains-proxies.md index 08b86f5ce..178e6465b 100644 --- a/docs/ingress-domains-proxies.md +++ b/docs/ingress-domains-proxies.md @@ -68,4 +68,46 @@ proxy: Note that in the case that gatekeepers are enabled, the same configurations are applied to the gatekeepers, unless the application override them on `harness.proxy.*`. -See also the [gatekeepers documentation](./accounts.md#secure-and-enpoint-with-the-gatekeeper). \ No newline at end of file +See also the [gatekeepers documentation](./accounts.md#secure-and-endpoint-with-the-gatekeeper). + +## Route pattern configuration + +Cloud Harness allows customizing which request paths are routed to an application +via paths and regular expression. There are two levels where this can be set: + +- **Global**: `ingress.path` and `ingress.pathType` in `deployment-configuration/helm/values.yaml` (applies to all apps by default). +- **Application**: `harness.gateway.path` and `harness.gateway.pathType` in an application's `values.yaml` (overrides the global value for that app). + +The Helm ingress template uses the application-level `harness.gateway` when present, +falling back to the global `ingress` otherwise. + +The default configuration uses Prefix paths for the highest compatibility: + +```yaml +path: / +pathType: Prefix +``` +The default configuration will work for a single application being served in the root directory within the domain with no exclusions. + + +Example with regular expression (global default in `deployment-configuration/helm/values.yaml`): + +```yaml +ingress: + # Example regex segment for routes (used in paths like '/(pattern)') + path: "/(.*)" + pathType: ImplementationSpecific +``` + +Example (application override in `applications//deploy/values.yaml`): + +```yaml +harness: + # route_pattern is used to build the Ingress path for the app + path: '/((?!(?:metrics)(?:/)?$).*)' # exclude only '/metrics' and '/metrics/' + pathType: ImplementationSpecific +``` + +Customization notes: +- The pattern is inserted into the generated Ingress `path` field. Make sure the regex + is valid for your ingress controller and matches the expected path syntax. diff --git a/docs/model/ApplicationHarnessConfig.md b/docs/model/ApplicationHarnessConfig.md index a589c11ab..f10c624f7 100644 --- a/docs/model/ApplicationHarnessConfig.md +++ b/docs/model/ApplicationHarnessConfig.md @@ -33,6 +33,7 @@ Name | Type | Description | Notes **sentry** | **bool** | | [optional] **proxy** | [**ProxyConf**](ProxyConf.md) | | [optional] **image_name** | **str** | Use this name for the image in place of the default directory name | [optional] +**gateway** | [**GatewayConfig**](GatewayConfig.md) | | [optional] ## Example diff --git a/docs/model/DatabaseConfig.md b/docs/model/DatabaseConfig.md new file mode 100644 index 000000000..c6b3bf210 --- /dev/null +++ b/docs/model/DatabaseConfig.md @@ -0,0 +1,32 @@ +# DatabaseConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**image** | **str** | | [optional] +**name** | **str** | | [optional] +**ports** | [**List[PortConfig]**](PortConfig.md) | | [optional] + +## Example + +```python +from cloudharness_model.models.database_config import DatabaseConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of DatabaseConfig from a JSON string +database_config_instance = DatabaseConfig.from_json(json) +# print the JSON string representation of the object +print(DatabaseConfig.to_json()) + +# convert the object into a dict +database_config_dict = database_config_instance.to_dict() +# create an instance of DatabaseConfig from a dict +database_config_from_dict = DatabaseConfig.from_dict(database_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/model/DatabaseDeploymentConfig.md b/docs/model/DatabaseDeploymentConfig.md index ae920cd1e..143a76cb9 100644 --- a/docs/model/DatabaseDeploymentConfig.md +++ b/docs/model/DatabaseDeploymentConfig.md @@ -17,6 +17,7 @@ Name | Type | Description | Notes **postgres** | **Dict[str, object]** | | [optional] **neo4j** | **object** | Neo4j database specific configuration | [optional] **resources** | [**DeploymentResourcesConf**](DeploymentResourcesConf.md) | | [optional] +**connect_string** | **str** | Specify if the database is external. If not null, auto deployment if set will not be used. Leave it as an empty string and the connect string will be provided as a secret to be provided at CI/CD (recommended) | [optional] ## Example diff --git a/docs/model/GatekeeperConf.md b/docs/model/GatekeeperConf.md index e27090fbf..268f65b34 100644 --- a/docs/model/GatekeeperConf.md +++ b/docs/model/GatekeeperConf.md @@ -8,6 +8,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **image** | **str** | | [optional] **replicas** | **int** | | [optional] +**resources** | [**DeploymentResourcesConf**](DeploymentResourcesConf.md) | | [optional] +**secret** | **str** | | [optional] ## Example diff --git a/docs/model/GatewayConfig.md b/docs/model/GatewayConfig.md new file mode 100644 index 000000000..89542402c --- /dev/null +++ b/docs/model/GatewayConfig.md @@ -0,0 +1,31 @@ +# GatewayConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**path_type** | **str** | Ingress path type | [optional] +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_config import GatewayConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayConfig from a JSON string +gateway_config_instance = GatewayConfig.from_json(json) +# print the JSON string representation of the object +print(GatewayConfig.to_json()) + +# convert the object into a dict +gateway_config_dict = gateway_config_instance.to_dict() +# create an instance of GatewayConfig from a dict +gateway_config_from_dict = GatewayConfig.from_dict(gateway_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/model/GatewayGlobalConfig.md b/docs/model/GatewayGlobalConfig.md new file mode 100644 index 000000000..49e128252 --- /dev/null +++ b/docs/model/GatewayGlobalConfig.md @@ -0,0 +1,36 @@ +# GatewayGlobalConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**auto** | **bool** | When true, enables automatic template | [optional] +**name** | **str** | | [optional] +**path_type** | **str** | Ingress path type | [optional] +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] +**ssl_redirect** | **bool** | | [optional] +**letsencrypt** | [**GatewayGlobalConfigAllOfLetsencrypt**](GatewayGlobalConfigAllOfLetsencrypt.md) | | [optional] +**enabled** | **bool** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config import GatewayGlobalConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfig from a JSON string +gateway_global_config_instance = GatewayGlobalConfig.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfig.to_json()) + +# convert the object into a dict +gateway_global_config_dict = gateway_global_config_instance.to_dict() +# create an instance of GatewayGlobalConfig from a dict +gateway_global_config_from_dict = GatewayGlobalConfig.from_dict(gateway_global_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md b/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md new file mode 100644 index 000000000..73c796322 --- /dev/null +++ b/docs/model/GatewayGlobalConfigAllOfLetsencrypt.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfLetsencrypt + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**email** | **str** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfLetsencrypt from a JSON string +gateway_global_config_all_of_letsencrypt_instance = GatewayGlobalConfigAllOfLetsencrypt.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfLetsencrypt.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_letsencrypt_dict = gateway_global_config_all_of_letsencrypt_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfLetsencrypt from a dict +gateway_global_config_all_of_letsencrypt_from_dict = GatewayGlobalConfigAllOfLetsencrypt.from_dict(gateway_global_config_all_of_letsencrypt_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/model/HarnessMainConfig.md b/docs/model/HarnessMainConfig.md index 7940b51cc..6b217efbf 100644 --- a/docs/model/HarnessMainConfig.md +++ b/docs/model/HarnessMainConfig.md @@ -20,7 +20,7 @@ Name | Type | Description | Notes **name** | **str** | Base name | [optional] **task_images** | **Dict[str, object]** | | [optional] **build_hash** | **str** | | [optional] -**ingress** | [**IngressConfig**](IngressConfig.md) | | [optional] +**ingress** | [**GatewayGlobalConfig**](GatewayGlobalConfig.md) | | [optional] ## Example diff --git a/docs/model/IngressConfig.md b/docs/model/IngressConfig.md deleted file mode 100644 index 5e2add2f3..000000000 --- a/docs/model/IngressConfig.md +++ /dev/null @@ -1,34 +0,0 @@ -# IngressConfig - - - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**auto** | **bool** | When true, enables automatic template | [optional] -**name** | **str** | | [optional] -**ssl_redirect** | **bool** | | [optional] -**letsencrypt** | [**IngressConfigAllOfLetsencrypt**](IngressConfigAllOfLetsencrypt.md) | | [optional] -**enabled** | **bool** | | [optional] - -## Example - -```python -from cloudharness_model.models.ingress_config import IngressConfig - -# TODO update the JSON string below -json = "{}" -# create an instance of IngressConfig from a JSON string -ingress_config_instance = IngressConfig.from_json(json) -# print the JSON string representation of the object -print(IngressConfig.to_json()) - -# convert the object into a dict -ingress_config_dict = ingress_config_instance.to_dict() -# create an instance of IngressConfig from a dict -ingress_config_from_dict = IngressConfig.from_dict(ingress_config_dict) -``` -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/docs/model/IngressConfigAllOfLetsencrypt.md b/docs/model/IngressConfigAllOfLetsencrypt.md deleted file mode 100644 index fa4ec0909..000000000 --- a/docs/model/IngressConfigAllOfLetsencrypt.md +++ /dev/null @@ -1,30 +0,0 @@ -# IngressConfigAllOfLetsencrypt - - - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**email** | **str** | | [optional] - -## Example - -```python -from cloudharness_model.models.ingress_config_all_of_letsencrypt import IngressConfigAllOfLetsencrypt - -# TODO update the JSON string below -json = "{}" -# create an instance of IngressConfigAllOfLetsencrypt from a JSON string -ingress_config_all_of_letsencrypt_instance = IngressConfigAllOfLetsencrypt.from_json(json) -# print the JSON string representation of the object -print(IngressConfigAllOfLetsencrypt.to_json()) - -# convert the object into a dict -ingress_config_all_of_letsencrypt_dict = ingress_config_all_of_letsencrypt_instance.to_dict() -# create an instance of IngressConfigAllOfLetsencrypt from a dict -ingress_config_all_of_letsencrypt_from_dict = IngressConfigAllOfLetsencrypt.from_dict(ingress_config_all_of_letsencrypt_dict) -``` -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/docs/model/PortConfig.md b/docs/model/PortConfig.md new file mode 100644 index 000000000..726c3c0a1 --- /dev/null +++ b/docs/model/PortConfig.md @@ -0,0 +1,31 @@ +# PortConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | | [optional] +**port** | **int** | | [optional] + +## Example + +```python +from cloudharness_model.models.port_config import PortConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of PortConfig from a JSON string +port_config_instance = PortConfig.from_json(json) +# print the JSON string representation of the object +print(PortConfig.to_json()) + +# convert the object into a dict +port_config_dict = port_config_instance.to_dict() +# create an instance of PortConfig from a dict +port_config_from_dict = PortConfig.from_dict(port_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/cloudharness-common/cloudharness/applications.py b/libraries/cloudharness-common/cloudharness/applications.py index cb73a6fc7..667e93e65 100644 --- a/libraries/cloudharness-common/cloudharness/applications.py +++ b/libraries/cloudharness-common/cloudharness/applications.py @@ -1,3 +1,4 @@ +import os from typing import List from cloudharness.utils.config import CloudharnessConfig, ConfigObject @@ -36,6 +37,10 @@ def is_sentry_enabled(self) -> bool: return self.harness.sentry def get_db_connection_string(self, **kwargs) -> str: + connect_string_path = "/opt/cloudharness/resources/db/connect_string" + if os.path.isfile(connect_string_path): + with open(connect_string_path) as f: + return f.read().strip() if not self.is_auto_db(): raise ConfigurationCallException( f"Cannot get configuration string: application {self.name} has no database enabled.") diff --git a/libraries/models/README.md b/libraries/models/README.md index a91663a5c..1555b03af 100644 --- a/libraries/models/README.md +++ b/libraries/models/README.md @@ -80,6 +80,7 @@ Class | Method | HTTP request | Description - [CDCEvent](docs/CDCEvent.md) - [CDCEventMeta](docs/CDCEventMeta.md) - [CpuMemoryConfig](docs/CpuMemoryConfig.md) + - [DatabaseConfig](docs/DatabaseConfig.md) - [DatabaseDeploymentConfig](docs/DatabaseDeploymentConfig.md) - [DeploymentAutoArtifactConfig](docs/DeploymentAutoArtifactConfig.md) - [DeploymentResourcesConf](docs/DeploymentResourcesConf.md) @@ -88,14 +89,16 @@ Class | Method | HTTP request | Description - [E2ETestsConfig](docs/E2ETestsConfig.md) - [FileResourcesConfig](docs/FileResourcesConfig.md) - [GatekeeperConf](docs/GatekeeperConf.md) + - [GatewayConfig](docs/GatewayConfig.md) + - [GatewayGlobalConfig](docs/GatewayGlobalConfig.md) + - [GatewayGlobalConfigAllOfLetsencrypt](docs/GatewayGlobalConfigAllOfLetsencrypt.md) - [GitDependencyConfig](docs/GitDependencyConfig.md) - [HarnessMainConfig](docs/HarnessMainConfig.md) - - [IngressConfig](docs/IngressConfig.md) - - [IngressConfigAllOfLetsencrypt](docs/IngressConfigAllOfLetsencrypt.md) - [JupyterHubConfig](docs/JupyterHubConfig.md) - [NameValue](docs/NameValue.md) - [NamedObject](docs/NamedObject.md) - [Organization](docs/Organization.md) + - [PortConfig](docs/PortConfig.md) - [ProxyConf](docs/ProxyConf.md) - [ProxyPayloadConf](docs/ProxyPayloadConf.md) - [ProxyTimeoutConf](docs/ProxyTimeoutConf.md) diff --git a/libraries/models/api/openapi.yaml b/libraries/models/api/openapi.yaml index b22a4fd2e..b2e4e5597 100644 --- a/libraries/models/api/openapi.yaml +++ b/libraries/models/api/openapi.yaml @@ -126,6 +126,7 @@ components: additionalProperties: true RegistrySecretConfig: description: '' + required: [] type: object properties: name: @@ -587,7 +588,7 @@ components: description: '' type: string ingress: - $ref: '#/components/schemas/IngressConfig' + $ref: '#/components/schemas/GatewayGlobalConfig' description: '' additionalProperties: true Organization: @@ -653,6 +654,16 @@ components: resources: $ref: '#/components/schemas/DeploymentResourcesConf' description: Database deployment resources + connect_string: + description: >- + Specify if the database is external. If not null, auto deployment if set will + not + + be used. Leva it as an empty string and the connect string will be provided + as + + a secret to be provided at CI/CD (recommended) + type: string - $ref: '#/components/schemas/AutoArtifactSpec' additionalProperties: true @@ -876,28 +887,6 @@ components: additionalProperties: true example: name: a name - IngressConfig: - description: '' - type: object - allOf: - - - type: object - properties: - ssl_redirect: - description: '' - type: boolean - letsencrypt: - description: '' - type: object - properties: - email: - type: string - enabled: - description: '' - type: boolean - - - $ref: '#/components/schemas/AutoArtifactSpec' - additionalProperties: true GatekeeperConf: title: Root Type for GatekeeperConf description: '' @@ -908,6 +897,12 @@ components: replicas: format: int32 type: integer + resources: + $ref: '#/components/schemas/DeploymentResourcesConf' + description: '' + secret: + description: '' + type: string example: image: 'quay.io/gogatekeeper/gatekeeper:2.14.3' replicas: 5 @@ -1035,4 +1030,80 @@ components: image_name: description: Use this name for the image in place of the default directory name type: string + gateway: + $ref: '#/components/schemas/GatewayConfig' + description: '' + additionalProperties: true + GatewayConfig: + description: '' + type: object + allOf: + - + required: [] + type: object + properties: + pathType: + description: 'Ingress path type ' + type: string + example: '"Prefix"' + path: + description: | + Default target path prefix for applications endpoints. + To use regular expressions (e.g.'/(pattern)'), also set `route_type` to + `ImplementationSpecific`. + type: string + example: '"/"' + additionalProperties: true + GatewayGlobalConfig: + description: '' + type: object + allOf: + - + type: object + properties: + ssl_redirect: + description: '' + type: boolean + letsencrypt: + description: '' + type: object + properties: + email: + type: string + enabled: + description: '' + type: boolean + - + $ref: '#/components/schemas/AutoArtifactSpec' + - + $ref: '#/components/schemas/GatewayConfig' + additionalProperties: true + PortConfig: + title: Root Type for PortConfig + description: '' + type: object + properties: + name: + type: string + port: + format: int32 + type: integer + example: + name: http + port: 8080 + DatabaseConfig: + description: '' + type: object + properties: + image: + description: '' + type: string + name: + description: '' + type: string + ports: + description: '' + type: array + items: + $ref: '#/components/schemas/PortConfig' additionalProperties: true diff --git a/libraries/models/cloudharness_model/models/__init__.py b/libraries/models/cloudharness_model/models/__init__.py index 2e5644932..f8b3b7ba8 100644 --- a/libraries/models/cloudharness_model/models/__init__.py +++ b/libraries/models/cloudharness_model/models/__init__.py @@ -27,6 +27,7 @@ from cloudharness_model.models.cdc_event import CDCEvent from cloudharness_model.models.cdc_event_meta import CDCEventMeta from cloudharness_model.models.cpu_memory_config import CpuMemoryConfig +from cloudharness_model.models.database_config import DatabaseConfig from cloudharness_model.models.database_deployment_config import DatabaseDeploymentConfig from cloudharness_model.models.deployment_auto_artifact_config import DeploymentAutoArtifactConfig from cloudharness_model.models.deployment_resources_conf import DeploymentResourcesConf @@ -35,14 +36,16 @@ from cloudharness_model.models.e2_e_tests_config import E2ETestsConfig from cloudharness_model.models.file_resources_config import FileResourcesConfig from cloudharness_model.models.gatekeeper_conf import GatekeeperConf +from cloudharness_model.models.gateway_config import GatewayConfig +from cloudharness_model.models.gateway_global_config import GatewayGlobalConfig +from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt from cloudharness_model.models.git_dependency_config import GitDependencyConfig from cloudharness_model.models.harness_main_config import HarnessMainConfig -from cloudharness_model.models.ingress_config import IngressConfig -from cloudharness_model.models.ingress_config_all_of_letsencrypt import IngressConfigAllOfLetsencrypt from cloudharness_model.models.jupyter_hub_config import JupyterHubConfig from cloudharness_model.models.name_value import NameValue from cloudharness_model.models.named_object import NamedObject from cloudharness_model.models.organization import Organization +from cloudharness_model.models.port_config import PortConfig from cloudharness_model.models.proxy_conf import ProxyConf from cloudharness_model.models.proxy_payload_conf import ProxyPayloadConf from cloudharness_model.models.proxy_timeout_conf import ProxyTimeoutConf diff --git a/libraries/models/cloudharness_model/models/application_harness_config.py b/libraries/models/cloudharness_model/models/application_harness_config.py index f4a74d88f..d886a593e 100644 --- a/libraries/models/cloudharness_model/models/application_harness_config.py +++ b/libraries/models/cloudharness_model/models/application_harness_config.py @@ -33,6 +33,7 @@ from cloudharness_model.models.deployment_auto_artifact_config import DeploymentAutoArtifactConfig from cloudharness_model.models.dockerfile_config import DockerfileConfig from cloudharness_model.models.file_resources_config import FileResourcesConfig +from cloudharness_model.models.gateway_config import GatewayConfig from cloudharness_model.models.jupyter_hub_config import JupyterHubConfig from cloudharness_model.models.name_value import NameValue from cloudharness_model.models.named_object import NamedObject @@ -71,8 +72,9 @@ class ApplicationHarnessConfig(CloudHarnessBaseModel): sentry: Optional[StrictBool] = None proxy: Optional[ProxyConf] = None image_name: Optional[StrictStr] = Field(default=None, description="Use this name for the image in place of the default directory name") + gateway: Optional[GatewayConfig] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["deployment", "service", "subdomain", "aliases", "domain", "dependencies", "secured", "uri_role_mapping", "secrets", "use_services", "database", "resources", "readinessProbe", "startupProbe", "livenessProbe", "sourceRoot", "name", "jupyterhub", "accounts", "test", "quotas", "env", "envmap", "dockerfile", "sentry", "proxy", "image_name"] + __properties: ClassVar[List[str]] = ["deployment", "service", "subdomain", "aliases", "domain", "dependencies", "secured", "uri_role_mapping", "secrets", "use_services", "database", "resources", "readinessProbe", "startupProbe", "livenessProbe", "sourceRoot", "name", "jupyterhub", "accounts", "test", "quotas", "env", "envmap", "dockerfile", "sentry", "proxy", "image_name", "gateway"] @field_validator('source_root') def source_root_validate_regular_expression(cls, value): @@ -168,6 +170,9 @@ def to_dict(self) -> Dict[str, Any]: # override the default output from pydantic by calling `to_dict()` of proxy if self.proxy: _dict['proxy'] = self.proxy.to_dict() + # override the default output from pydantic by calling `to_dict()` of gateway + if self.gateway: + _dict['gateway'] = self.gateway.to_dict() # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): @@ -216,7 +221,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "dockerfile": DockerfileConfig.from_dict(obj["dockerfile"]) if obj.get("dockerfile") is not None else None, "sentry": obj.get("sentry"), "proxy": ProxyConf.from_dict(obj["proxy"]) if obj.get("proxy") is not None else None, - "image_name": obj.get("image_name") + "image_name": obj.get("image_name"), + "gateway": GatewayConfig.from_dict(obj["gateway"]) if obj.get("gateway") is not None else None }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/database_config.py b/libraries/models/cloudharness_model/models/database_config.py new file mode 100644 index 000000000..a2a914c56 --- /dev/null +++ b/libraries/models/cloudharness_model/models/database_config.py @@ -0,0 +1,95 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib +from cloudharness_model.models.port_config import PortConfig + +class DatabaseConfig(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + image: Optional[StrictStr] = None + name: Optional[StrictStr] = None + ports: Optional[List[PortConfig]] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["image", "name", "ports"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in ports (list) + _items = [] + if self.ports: + for _item_ports in self.ports: + if _item_ports: + _items.append(_item_ports.to_dict()) + _dict['ports'] = _items + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of DatabaseConfig from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _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 + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/database_deployment_config.py b/libraries/models/cloudharness_model/models/database_deployment_config.py index d87a45ad0..c059557e5 100644 --- a/libraries/models/cloudharness_model/models/database_deployment_config.py +++ b/libraries/models/cloudharness_model/models/database_deployment_config.py @@ -42,8 +42,9 @@ class DatabaseDeploymentConfig(CloudHarnessBaseModel): postgres: Optional[Dict[str, Any]] = None neo4j: Optional[Any] = Field(default=None, description="Neo4j database specific configuration") resources: Optional[DeploymentResourcesConf] = None + connect_string: Optional[StrictStr] = Field(default=None, description="Specify if the database is external. If not null, auto deployment if set will not be used. Leva it as an empty string and the connect string will be provided as a secret to be provided at CI/CD (recommended)") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["auto", "name", "type", "size", "user", "pass", "image_ref", "mongo", "postgres", "neo4j", "resources"] + __properties: ClassVar[List[str]] = ["auto", "name", "type", "size", "user", "pass", "image_ref", "mongo", "postgres", "neo4j", "resources", "connect_string"] @field_validator('type') def type_validate_regular_expression(cls, value): @@ -110,7 +111,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "mongo": obj.get("mongo"), "postgres": obj.get("postgres"), "neo4j": obj.get("neo4j"), - "resources": DeploymentResourcesConf.from_dict(obj["resources"]) if obj.get("resources") is not None else None + "resources": DeploymentResourcesConf.from_dict(obj["resources"]) if obj.get("resources") is not None else None, + "connect_string": obj.get("connect_string") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/gatekeeper_conf.py b/libraries/models/cloudharness_model/models/gatekeeper_conf.py index 2e757429a..e83212f59 100644 --- a/libraries/models/cloudharness_model/models/gatekeeper_conf.py +++ b/libraries/models/cloudharness_model/models/gatekeeper_conf.py @@ -25,6 +25,7 @@ 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.deployment_resources_conf import DeploymentResourcesConf class GatekeeperConf(CloudHarnessBaseModel): """ @@ -32,8 +33,10 @@ class GatekeeperConf(CloudHarnessBaseModel): """ # noqa: E501 image: Optional[StrictStr] = None replicas: Optional[StrictInt] = None + resources: Optional[DeploymentResourcesConf] = None + secret: Optional[StrictStr] = None additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["image", "replicas"] + __properties: ClassVar[List[str]] = ["image", "replicas", "resources", "secret"] def to_dict(self) -> Dict[str, Any]: """Return the dictionary representation of the model using alias. @@ -55,6 +58,9 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of resources + if self.resources: + _dict['resources'] = self.resources.to_dict() # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): @@ -73,7 +79,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: _obj = cls.model_validate({ "image": obj.get("image"), - "replicas": obj.get("replicas") + "replicas": obj.get("replicas"), + "resources": DeploymentResourcesConf.from_dict(obj["resources"]) if obj.get("resources") is not None else None, + "secret": obj.get("secret") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/gateway_config.py b/libraries/models/cloudharness_model/models/gateway_config.py new file mode 100644 index 000000000..9e195cbf0 --- /dev/null +++ b/libraries/models/cloudharness_model/models/gateway_config.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib + +class GatewayConfig(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + path_type: Optional[StrictStr] = Field(default=None, description="Ingress path type ", alias="pathType") + path: Optional[StrictStr] = Field(default=None, description="Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. ") + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["pathType", "path"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GatewayConfig from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "pathType": obj.get("pathType"), + "path": obj.get("path") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/gateway_global_config.py b/libraries/models/cloudharness_model/models/gateway_global_config.py new file mode 100644 index 000000000..125533447 --- /dev/null +++ b/libraries/models/cloudharness_model/models/gateway_global_config.py @@ -0,0 +1,99 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib +from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt + +class GatewayGlobalConfig(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + auto: Optional[StrictBool] = Field(default=None, description="When true, enables automatic template") + name: Optional[StrictStr] = None + path_type: Optional[StrictStr] = Field(default=None, description="Ingress path type ", alias="pathType") + path: Optional[StrictStr] = Field(default=None, description="Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. ") + ssl_redirect: Optional[StrictBool] = None + letsencrypt: Optional[GatewayGlobalConfigAllOfLetsencrypt] = None + enabled: Optional[StrictBool] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["auto", "name", "pathType", "path", "ssl_redirect", "letsencrypt", "enabled"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of letsencrypt + if self.letsencrypt: + _dict['letsencrypt'] = self.letsencrypt.to_dict() + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GatewayGlobalConfig from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "auto": obj.get("auto"), + "name": obj.get("name"), + "pathType": obj.get("pathType"), + "path": obj.get("path"), + "ssl_redirect": obj.get("ssl_redirect"), + "letsencrypt": GatewayGlobalConfigAllOfLetsencrypt.from_dict(obj["letsencrypt"]) if obj.get("letsencrypt") is not None else None, + "enabled": obj.get("enabled") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py new file mode 100644 index 000000000..f402310d8 --- /dev/null +++ b/libraries/models/cloudharness_model/models/gateway_global_config_all_of_letsencrypt.py @@ -0,0 +1,83 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib + +class GatewayGlobalConfigAllOfLetsencrypt(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + email: Optional[StrictStr] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["email"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GatewayGlobalConfigAllOfLetsencrypt from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "email": obj.get("email") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/harness_main_config.py b/libraries/models/cloudharness_model/models/harness_main_config.py index 58de55813..ba25128fe 100644 --- a/libraries/models/cloudharness_model/models/harness_main_config.py +++ b/libraries/models/cloudharness_model/models/harness_main_config.py @@ -27,7 +27,7 @@ import importlib from cloudharness_model.models.application_config import ApplicationConfig from cloudharness_model.models.backup_config import BackupConfig -from cloudharness_model.models.ingress_config import IngressConfig +from cloudharness_model.models.gateway_global_config import GatewayGlobalConfig from cloudharness_model.models.name_value import NameValue from cloudharness_model.models.registry_config import RegistryConfig @@ -49,7 +49,7 @@ class HarnessMainConfig(CloudHarnessBaseModel): name: Optional[StrictStr] = Field(default=None, description="Base name") task_images: Optional[Dict[str, Any]] = Field(default=None, alias="task-images") build_hash: Optional[StrictStr] = None - ingress: Optional[IngressConfig] = None + ingress: Optional[GatewayGlobalConfig] = None additional_properties: Dict[str, Any] = {} __properties: ClassVar[List[str]] = ["local", "secured_gatekeepers", "domain", "namespace", "mainapp", "registry", "tag", "apps", "env", "privenv", "backup", "name", "task-images", "build_hash", "ingress"] @@ -139,7 +139,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "name": obj.get("name"), "task-images": obj.get("task-images"), "build_hash": obj.get("build_hash"), - "ingress": IngressConfig.from_dict(obj["ingress"]) if obj.get("ingress") is not None else None + "ingress": GatewayGlobalConfig.from_dict(obj["ingress"]) if obj.get("ingress") is not None else None }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/ingress_config.py b/libraries/models/cloudharness_model/models/ingress_config.py index 9e9154f5a..7d9a35250 100644 --- a/libraries/models/cloudharness_model/models/ingress_config.py +++ b/libraries/models/cloudharness_model/models/ingress_config.py @@ -25,19 +25,15 @@ 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.ingress_config_all_of_letsencrypt import IngressConfigAllOfLetsencrypt class IngressConfig(CloudHarnessBaseModel): """ """ # noqa: E501 - auto: Optional[StrictBool] = Field(default=None, description="When true, enables automatic template") - name: Optional[StrictStr] = None - ssl_redirect: Optional[StrictBool] = None - letsencrypt: Optional[IngressConfigAllOfLetsencrypt] = None - enabled: Optional[StrictBool] = None + path_type: StrictStr = Field(description="Ingress path type ", alias="pathType") + path: StrictStr = Field(description="Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. ") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["auto", "name", "ssl_redirect", "letsencrypt", "enabled"] + __properties: ClassVar[List[str]] = ["pathType", "path"] def to_dict(self) -> Dict[str, Any]: """Return the dictionary representation of the model using alias. @@ -59,9 +55,6 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) - # override the default output from pydantic by calling `to_dict()` of letsencrypt - if self.letsencrypt: - _dict['letsencrypt'] = self.letsencrypt.to_dict() # puts key-value pairs in additional_properties in the top level if self.additional_properties is not None: for _key, _value in self.additional_properties.items(): @@ -79,11 +72,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "auto": obj.get("auto"), - "name": obj.get("name"), - "ssl_redirect": obj.get("ssl_redirect"), - "letsencrypt": IngressConfigAllOfLetsencrypt.from_dict(obj["letsencrypt"]) if obj.get("letsencrypt") is not None else None, - "enabled": obj.get("enabled") + "pathType": obj.get("pathType"), + "path": obj.get("path") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/cloudharness_model/models/ingress_global_config.py b/libraries/models/cloudharness_model/models/ingress_global_config.py new file mode 100644 index 000000000..1bd279baa --- /dev/null +++ b/libraries/models/cloudharness_model/models/ingress_global_config.py @@ -0,0 +1,99 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib +from cloudharness_model.models.ingress_global_config_all_of_letsencrypt import IngressGlobalConfigAllOfLetsencrypt + +class IngressGlobalConfig(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + auto: Optional[StrictBool] = Field(default=None, description="When true, enables automatic template") + name: Optional[StrictStr] = None + path_type: StrictStr = Field(description="Ingress path type ", alias="pathType") + path: StrictStr = Field(description="Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. ") + ssl_redirect: Optional[StrictBool] = None + letsencrypt: Optional[IngressGlobalConfigAllOfLetsencrypt] = None + enabled: Optional[StrictBool] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["auto", "name", "pathType", "path", "ssl_redirect", "letsencrypt", "enabled"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of letsencrypt + if self.letsencrypt: + _dict['letsencrypt'] = self.letsencrypt.to_dict() + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of IngressGlobalConfig from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "auto": obj.get("auto"), + "name": obj.get("name"), + "pathType": obj.get("pathType"), + "path": obj.get("path"), + "ssl_redirect": obj.get("ssl_redirect"), + "letsencrypt": IngressGlobalConfigAllOfLetsencrypt.from_dict(obj["letsencrypt"]) if obj.get("letsencrypt") is not None else None, + "enabled": obj.get("enabled") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/ingress_global_config_all_of_letsencrypt.py b/libraries/models/cloudharness_model/models/ingress_global_config_all_of_letsencrypt.py new file mode 100644 index 000000000..871aa2fc2 --- /dev/null +++ b/libraries/models/cloudharness_model/models/ingress_global_config_all_of_letsencrypt.py @@ -0,0 +1,83 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib + +class IngressGlobalConfigAllOfLetsencrypt(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + email: Optional[StrictStr] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["email"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of IngressGlobalConfigAllOfLetsencrypt from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "email": obj.get("email") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/cloudharness_model/models/port_config.py b/libraries/models/cloudharness_model/models/port_config.py new file mode 100644 index 000000000..78ebc1343 --- /dev/null +++ b/libraries/models/cloudharness_model/models/port_config.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +""" + cloudharness + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.0.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +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 BaseModel, Field, field_validator, StrictStr, StrictBool, StrictInt, StrictFloat +from typing import ClassVar, List, Dict, Any, Union, Optional, Annotated +import importlib + +class PortConfig(CloudHarnessBaseModel): + """ + + """ # noqa: E501 + name: Optional[StrictStr] = None + port: Optional[StrictInt] = None + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = ["name", "port"] + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set([ + "additional_properties", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of PortConfig from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "name": obj.get("name"), + "port": obj.get("port") + }) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj + + diff --git a/libraries/models/docs/ApplicationHarnessConfig.md b/libraries/models/docs/ApplicationHarnessConfig.md index a589c11ab..f10c624f7 100644 --- a/libraries/models/docs/ApplicationHarnessConfig.md +++ b/libraries/models/docs/ApplicationHarnessConfig.md @@ -33,6 +33,7 @@ Name | Type | Description | Notes **sentry** | **bool** | | [optional] **proxy** | [**ProxyConf**](ProxyConf.md) | | [optional] **image_name** | **str** | Use this name for the image in place of the default directory name | [optional] +**gateway** | [**GatewayConfig**](GatewayConfig.md) | | [optional] ## Example diff --git a/libraries/models/docs/DatabaseConfig.md b/libraries/models/docs/DatabaseConfig.md new file mode 100644 index 000000000..c6b3bf210 --- /dev/null +++ b/libraries/models/docs/DatabaseConfig.md @@ -0,0 +1,32 @@ +# DatabaseConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**image** | **str** | | [optional] +**name** | **str** | | [optional] +**ports** | [**List[PortConfig]**](PortConfig.md) | | [optional] + +## Example + +```python +from cloudharness_model.models.database_config import DatabaseConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of DatabaseConfig from a JSON string +database_config_instance = DatabaseConfig.from_json(json) +# print the JSON string representation of the object +print(DatabaseConfig.to_json()) + +# convert the object into a dict +database_config_dict = database_config_instance.to_dict() +# create an instance of DatabaseConfig from a dict +database_config_from_dict = DatabaseConfig.from_dict(database_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/DatabaseDeploymentConfig.md b/libraries/models/docs/DatabaseDeploymentConfig.md index ae920cd1e..9babd8437 100644 --- a/libraries/models/docs/DatabaseDeploymentConfig.md +++ b/libraries/models/docs/DatabaseDeploymentConfig.md @@ -17,6 +17,7 @@ Name | Type | Description | Notes **postgres** | **Dict[str, object]** | | [optional] **neo4j** | **object** | Neo4j database specific configuration | [optional] **resources** | [**DeploymentResourcesConf**](DeploymentResourcesConf.md) | | [optional] +**connect_string** | **str** | Specify if the database is external. If not null, auto deployment if set will not be used. Leva it as an empty string and the connect string will be provided as a secret to be provided at CI/CD (recommended) | [optional] ## Example diff --git a/libraries/models/docs/GatekeeperConf.md b/libraries/models/docs/GatekeeperConf.md index e27090fbf..268f65b34 100644 --- a/libraries/models/docs/GatekeeperConf.md +++ b/libraries/models/docs/GatekeeperConf.md @@ -8,6 +8,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **image** | **str** | | [optional] **replicas** | **int** | | [optional] +**resources** | [**DeploymentResourcesConf**](DeploymentResourcesConf.md) | | [optional] +**secret** | **str** | | [optional] ## Example diff --git a/libraries/models/docs/GatewayConfig.md b/libraries/models/docs/GatewayConfig.md new file mode 100644 index 000000000..89542402c --- /dev/null +++ b/libraries/models/docs/GatewayConfig.md @@ -0,0 +1,31 @@ +# GatewayConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**path_type** | **str** | Ingress path type | [optional] +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_config import GatewayConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayConfig from a JSON string +gateway_config_instance = GatewayConfig.from_json(json) +# print the JSON string representation of the object +print(GatewayConfig.to_json()) + +# convert the object into a dict +gateway_config_dict = gateway_config_instance.to_dict() +# create an instance of GatewayConfig from a dict +gateway_config_from_dict = GatewayConfig.from_dict(gateway_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/GatewayGlobalConfig.md b/libraries/models/docs/GatewayGlobalConfig.md new file mode 100644 index 000000000..49e128252 --- /dev/null +++ b/libraries/models/docs/GatewayGlobalConfig.md @@ -0,0 +1,36 @@ +# GatewayGlobalConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**auto** | **bool** | When true, enables automatic template | [optional] +**name** | **str** | | [optional] +**path_type** | **str** | Ingress path type | [optional] +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | [optional] +**ssl_redirect** | **bool** | | [optional] +**letsencrypt** | [**GatewayGlobalConfigAllOfLetsencrypt**](GatewayGlobalConfigAllOfLetsencrypt.md) | | [optional] +**enabled** | **bool** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config import GatewayGlobalConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfig from a JSON string +gateway_global_config_instance = GatewayGlobalConfig.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfig.to_json()) + +# convert the object into a dict +gateway_global_config_dict = gateway_global_config_instance.to_dict() +# create an instance of GatewayGlobalConfig from a dict +gateway_global_config_from_dict = GatewayGlobalConfig.from_dict(gateway_global_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md b/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md new file mode 100644 index 000000000..73c796322 --- /dev/null +++ b/libraries/models/docs/GatewayGlobalConfigAllOfLetsencrypt.md @@ -0,0 +1,30 @@ +# GatewayGlobalConfigAllOfLetsencrypt + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**email** | **str** | | [optional] + +## Example + +```python +from cloudharness_model.models.gateway_global_config_all_of_letsencrypt import GatewayGlobalConfigAllOfLetsencrypt + +# TODO update the JSON string below +json = "{}" +# create an instance of GatewayGlobalConfigAllOfLetsencrypt from a JSON string +gateway_global_config_all_of_letsencrypt_instance = GatewayGlobalConfigAllOfLetsencrypt.from_json(json) +# print the JSON string representation of the object +print(GatewayGlobalConfigAllOfLetsencrypt.to_json()) + +# convert the object into a dict +gateway_global_config_all_of_letsencrypt_dict = gateway_global_config_all_of_letsencrypt_instance.to_dict() +# create an instance of GatewayGlobalConfigAllOfLetsencrypt from a dict +gateway_global_config_all_of_letsencrypt_from_dict = GatewayGlobalConfigAllOfLetsencrypt.from_dict(gateway_global_config_all_of_letsencrypt_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/HarnessMainConfig.md b/libraries/models/docs/HarnessMainConfig.md index 7940b51cc..6b217efbf 100644 --- a/libraries/models/docs/HarnessMainConfig.md +++ b/libraries/models/docs/HarnessMainConfig.md @@ -20,7 +20,7 @@ Name | Type | Description | Notes **name** | **str** | Base name | [optional] **task_images** | **Dict[str, object]** | | [optional] **build_hash** | **str** | | [optional] -**ingress** | [**IngressConfig**](IngressConfig.md) | | [optional] +**ingress** | [**GatewayGlobalConfig**](GatewayGlobalConfig.md) | | [optional] ## Example diff --git a/libraries/models/docs/IngressConfig.md b/libraries/models/docs/IngressConfig.md index 5e2add2f3..740b04cde 100644 --- a/libraries/models/docs/IngressConfig.md +++ b/libraries/models/docs/IngressConfig.md @@ -6,11 +6,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**auto** | **bool** | When true, enables automatic template | [optional] -**name** | **str** | | [optional] -**ssl_redirect** | **bool** | | [optional] -**letsencrypt** | [**IngressConfigAllOfLetsencrypt**](IngressConfigAllOfLetsencrypt.md) | | [optional] -**enabled** | **bool** | | [optional] +**path_type** | **str** | Ingress path type | +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | ## Example diff --git a/libraries/models/docs/IngressGlobalConfig.md b/libraries/models/docs/IngressGlobalConfig.md new file mode 100644 index 000000000..023005106 --- /dev/null +++ b/libraries/models/docs/IngressGlobalConfig.md @@ -0,0 +1,36 @@ +# IngressGlobalConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**auto** | **bool** | When true, enables automatic template | [optional] +**name** | **str** | | [optional] +**path_type** | **str** | Ingress path type | +**path** | **str** | Default target path prefix for applications endpoints. To use regular expressions (e.g.'/(pattern)'), also set `route_type` to `ImplementationSpecific`. | +**ssl_redirect** | **bool** | | [optional] +**letsencrypt** | [**IngressGlobalConfigAllOfLetsencrypt**](IngressGlobalConfigAllOfLetsencrypt.md) | | [optional] +**enabled** | **bool** | | [optional] + +## Example + +```python +from cloudharness_model.models.ingress_global_config import IngressGlobalConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of IngressGlobalConfig from a JSON string +ingress_global_config_instance = IngressGlobalConfig.from_json(json) +# print the JSON string representation of the object +print(IngressGlobalConfig.to_json()) + +# convert the object into a dict +ingress_global_config_dict = ingress_global_config_instance.to_dict() +# create an instance of IngressGlobalConfig from a dict +ingress_global_config_from_dict = IngressGlobalConfig.from_dict(ingress_global_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/IngressGlobalConfigAllOfLetsencrypt.md b/libraries/models/docs/IngressGlobalConfigAllOfLetsencrypt.md new file mode 100644 index 000000000..f376f1377 --- /dev/null +++ b/libraries/models/docs/IngressGlobalConfigAllOfLetsencrypt.md @@ -0,0 +1,30 @@ +# IngressGlobalConfigAllOfLetsencrypt + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**email** | **str** | | [optional] + +## Example + +```python +from cloudharness_model.models.ingress_global_config_all_of_letsencrypt import IngressGlobalConfigAllOfLetsencrypt + +# TODO update the JSON string below +json = "{}" +# create an instance of IngressGlobalConfigAllOfLetsencrypt from a JSON string +ingress_global_config_all_of_letsencrypt_instance = IngressGlobalConfigAllOfLetsencrypt.from_json(json) +# print the JSON string representation of the object +print(IngressGlobalConfigAllOfLetsencrypt.to_json()) + +# convert the object into a dict +ingress_global_config_all_of_letsencrypt_dict = ingress_global_config_all_of_letsencrypt_instance.to_dict() +# create an instance of IngressGlobalConfigAllOfLetsencrypt from a dict +ingress_global_config_all_of_letsencrypt_from_dict = IngressGlobalConfigAllOfLetsencrypt.from_dict(ingress_global_config_all_of_letsencrypt_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/libraries/models/docs/PortConfig.md b/libraries/models/docs/PortConfig.md new file mode 100644 index 000000000..726c3c0a1 --- /dev/null +++ b/libraries/models/docs/PortConfig.md @@ -0,0 +1,31 @@ +# PortConfig + + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | | [optional] +**port** | **int** | | [optional] + +## Example + +```python +from cloudharness_model.models.port_config import PortConfig + +# TODO update the JSON string below +json = "{}" +# create an instance of PortConfig from a JSON string +port_config_instance = PortConfig.from_json(json) +# print the JSON string representation of the object +print(PortConfig.to_json()) + +# convert the object into a dict +port_config_dict = port_config_instance.to_dict() +# create an instance of PortConfig from a dict +port_config_from_dict = PortConfig.from_dict(port_config_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py index 1c520713b..691cf40df 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py +++ b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py @@ -400,6 +400,14 @@ def adjust_build_steps(index): secret_name = secret.replace("_", "__") arguments["custom_values"].append( "apps_%s_harness_secrets_%s=${{%s}}" % (app_name.replace("_", "__"), secret_name, secret_name.upper())) + # Add connect_string as a secret custom_value for apps that have it set to empty + for app_name, app in helm_values.apps.items(): + if app.harness.database and app.harness.database.get("connect_string") == "": + var_name = f"{app_name.upper().replace('-', '_')}_DB_CONNECT_STRING" + arguments["custom_values"].append( + "apps_%s_harness_database_connect__string=${{%s}}" % ( + app_name.replace("_", "__"), var_name) + ) # Add registry secret value secret if registry secret name is set registry = getattr(helm_values, "registry", None) secret = getattr(registry, "secret", None) diff --git a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-connectstring.yaml b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-connectstring.yaml new file mode 100644 index 000000000..ec41dd3f3 --- /dev/null +++ b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-connectstring.yaml @@ -0,0 +1,2 @@ +harness: + database: {connect_string: "", type: postgres} \ No newline at end of file diff --git a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withoutdb.yaml b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withoutdb.yaml index e69de29bb..0c56e1d0e 100644 --- a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withoutdb.yaml +++ b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-withoutdb.yaml @@ -0,0 +1,4 @@ +harness: + database: + auto: false + type: \ No newline at end of file diff --git a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values.yaml b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values.yaml index e386702a6..8c662f66d 100644 --- a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values.yaml +++ b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values.yaml @@ -1,6 +1,10 @@ harness: name: "I'm useless" subdomain: mysubdomain + database: + auto: true + type: mongo + external_connect_string: "" dependencies: soft: - legacy diff --git a/tools/deployment-cli-tools/tests/test_codefresh.py b/tools/deployment-cli-tools/tests/test_codefresh.py index 5f096f334..4ce13aef7 100644 --- a/tools/deployment-cli-tools/tests/test_codefresh.py +++ b/tools/deployment-cli-tools/tests/test_codefresh.py @@ -331,6 +331,40 @@ def test_create_codefresh_configuration_nobuild(): assert "publish_myapp-mytask" in l1_steps["publish"]["steps"] +def test_codefresh_db_connect_string_secret(): + """When an app has database.connect_string set to '', a custom_values entry must be added to the deployment step.""" + values = create_helm_chart( + [CLOUDHARNESS_ROOT, RESOURCES], + output_path=OUT, + include=['myapp'], + exclude=['events'], + domain="my.local", + namespace='test', + env='connectstring', + local=False, + tag=1, + registry='reg' + ) + try: + root_paths = preprocess_build_overrides( + root_paths=[CLOUDHARNESS_ROOT, RESOURCES], + helm_values=values, + merge_build_path=BUILD_MERGE_DIR + ) + build_included = [app['harness']['name'] + for app in values['apps'].values() if 'harness' in app] + cf = create_codefresh_deployment_scripts(root_paths, include=build_included, + envs=['dev'], + base_image_name=values['name'], + helm_values=values, save=False) + custom_values = cf['steps']['deployment']['arguments']['custom_values'] + expected = "apps_myapp_harness_database_connect__string=${{MYAPP_DB_CONNECT_STRING}}" + assert expected in custom_values, \ + f"Expected custom_value entry for connect_string not found. Got: {custom_values}" + finally: + shutil.rmtree(BUILD_MERGE_DIR, ignore_errors=True) + + def test_sort_parallel_steps_alphabetically(): """Sub-steps inside parallel steps must be sorted alphabetically by name.""" steps = { @@ -483,7 +517,7 @@ def test_steps_ordered_by_stage_in_generated_config(): assert step_stage_indices == sorted(step_stage_indices), \ "Steps are not ordered by stage. Got stages: " + str( [step.get('stage') for step in steps.values() if step and isinstance(step, dict)] - ) + ) finally: import shutil shutil.rmtree(BUILD_MERGE_DIR, ignore_errors=True)