diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee78cdcc..313e254f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ ### Fixed +### Added +- Migrate Python Flask Quickstarter to FastAPI ([#1170](https://github.com/opendevstack/ods-quickstarters/issues/1170)) + ## [4.13.1] - 2026-07-30 ### Fixed - Fix jenkins agents python issues with UV ([#1167](https://github.com/opendevstack/ods-quickstarters/pull/1167)) diff --git a/be-python-fast-api/Chart.yaml.template b/be-python-fast-api/Chart.yaml.template new file mode 100644 index 000000000..2c1f7a8dd --- /dev/null +++ b/be-python-fast-api/Chart.yaml.template @@ -0,0 +1,24 @@ +apiVersion: v2 +name: @component_id@ +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.0.0" diff --git a/be-python-flask/Jenkinsfile b/be-python-fast-api/Jenkinsfile similarity index 57% rename from be-python-flask/Jenkinsfile rename to be-python-fast-api/Jenkinsfile index 5c18f8bc7..a943e6779 100644 --- a/be-python-flask/Jenkinsfile +++ b/be-python-fast-api/Jenkinsfile @@ -20,12 +20,17 @@ odsQuickstarterPipeline( odsQuickstarterStageCopyFiles(context) - odsQuickstarterStageCreateOpenShiftResources( - context, - [directory: 'common/ocp-config/component-environment'] - ) - odsQuickstarterStageRenderJenkinsfile(context) odsQuickstarterStageRenderSonarProperties(context) + + renderHelmChart(context) +} + +def renderHelmChart(def context) { + def relativeSourceFilePath = "Chart.yaml.template" + def relativeDestinationFilePath = "chart/Chart.yaml" + def absoluteSourceFilePath = "${context.sourceDir}/${relativeSourceFilePath}" + def absoluteDestinationFilePath = "${context.targetDir}/${relativeDestinationFilePath}" + sh(script: "sed 's|@component_id@|${context.componentId}|g' ${absoluteSourceFilePath} > ${absoluteDestinationFilePath}", label: "Render Helm Chart.yaml file") } diff --git a/be-python-flask/Jenkinsfile.template b/be-python-fast-api/Jenkinsfile.template similarity index 85% rename from be-python-flask/Jenkinsfile.template rename to be-python-fast-api/Jenkinsfile.template index f16391d90..d60a9e5eb 100644 --- a/be-python-flask/Jenkinsfile.template +++ b/be-python-fast-api/Jenkinsfile.template @@ -20,7 +20,13 @@ odsComponentPipeline( ] ]) } - odsComponentStageRolloutOpenShiftDeployment(context) + def releaseName = context.componentId + def componentId = context.componentId + odsComponentStageRolloutOpenShiftDeployment(context, [ + 'selector': "app.kubernetes.io/instance=${releaseName},app.kubernetes.io/name=${componentId}", + 'helmEnvBasedValuesFiles': ["values.env.yaml"], + 'helmReleaseName': releaseName + ]) } def stageTestSuite(def context) { diff --git a/be-python-fast-api/README.md b/be-python-fast-api/README.md new file mode 100644 index 000000000..185b71b32 --- /dev/null +++ b/be-python-fast-api/README.md @@ -0,0 +1,65 @@ +# Python FastAPI Quickstarter (be-python-fast-api) + +Documentation is located in our [official documentation](https://www.opendevstack.org/ods-documentation/ods-quickstarters/latest/index.html) + +Please update documentation in the [antora page directory](https://github.com/opendevstack/ods-quickstarters/tree/master/docs/modules/ROOT/pages) + +Tested thru [automated tests](../tests/be-python-flask) + +## Purpose + +This Quickstarter creates a Python backend service using [FastAPI](https://fastapi.tiangolo.com/) served by [Uvicorn](https://www.uvicorn.org/). It includes a Helm chart for deployment on OpenShift/Kubernetes. + +## Folder structure and important files + +- `src/`: Application source code + - `src/main.py`: FastAPI application entry point with `/` and `/health` endpoints +- `tests/`: Unit tests using pytest and FastAPI's TestClient +- `docker/`: Files for building the container image + - `docker/Dockerfile`: Container definition (UBI9/Python 3.12 base) + - `docker/run.sh`: Starts the app with `uvicorn` +- `requirements.txt`: Production dependencies (fastapi, uvicorn) +- `tests_requirements.txt`: Test dependencies (pytest, mypy, flake8, httpx) +- `chart/`: Helm chart for deploying the component + - `chart/values.yaml`: Default values (resources, probes, service config) + - `chart/values.dev.yaml`: Overrides for the `dev` environment + - `chart/values.test.yaml`: Overrides for the `test` environment (2 replicas) + - `chart/values.prod.yaml`: Overrides for the `prod` environment (2 replicas) + +## Testing locally + +### Running unit tests + +```bash +python3.12 -m venv venv +. venv/bin/activate +pip install -r tests_requirements.txt +PYTHONPATH=src python3.12 -m pytest tests/ +``` + +### Running the application locally + +```bash +. venv/bin/activate +PYTHONPATH=src uvicorn main:app --reload --port 8080 +``` + +### Building the container + +```bash +cp -r src docker/app +cp requirements.txt docker/app +docker build -t testing/my-component:$(git rev-parse --short=8 HEAD) docker/ +``` + +### Helm chart linting + +```bash +helm lint chart/ +``` + +### Helm chart template processing test + +```bash +helm --debug template chart/ --set image.path=testing --set image.name=my-component --set image.tag=$(git rev-parse --short=8 HEAD) +``` diff --git a/be-python-flask/files/.coveragerc b/be-python-fast-api/files/.coveragerc similarity index 100% rename from be-python-flask/files/.coveragerc rename to be-python-fast-api/files/.coveragerc diff --git a/be-python-flask/files/.pre-commit-config.yaml b/be-python-fast-api/files/.pre-commit-config.yaml similarity index 100% rename from be-python-flask/files/.pre-commit-config.yaml rename to be-python-fast-api/files/.pre-commit-config.yaml diff --git a/be-python-fast-api/files/chart/.helmignore b/be-python-fast-api/files/chart/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/be-python-fast-api/files/chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/be-python-fast-api/files/chart/Chart.yaml b/be-python-fast-api/files/chart/Chart.yaml new file mode 100644 index 000000000..64267e8c4 --- /dev/null +++ b/be-python-fast-api/files/chart/Chart.yaml @@ -0,0 +1,26 @@ +# IMPORTANT: Content will be recreated from the Chart.yaml.template file by the Jenkins shared library provision job +# NOTE: The content is provided for testing purposes +apiVersion: v2 +name: Your helm chart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.0.0" diff --git a/be-python-fast-api/files/chart/templates/NOTES.txt b/be-python-fast-api/files/chart/templates/NOTES.txt new file mode 100644 index 000000000..1708ecef9 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/NOTES.txt @@ -0,0 +1,10 @@ +Component '{{ include "chart.fullname" . }}' on version '{{ .Values.imageTag }}' released with Helm! +{{- if .Values.ingress.enabled }} +The component is exposed via the following routes: +{{- $appUrl := .Values.appUrl -}} +{{- range .Values.ingress.hosts }} +{{ printf "https://%s" .host }} +{{- end }} +{{- else }} +The component is not exposed. +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/_affinity.tpl b/be-python-fast-api/files/chart/templates/_affinity.tpl new file mode 100644 index 000000000..cc9c3519c --- /dev/null +++ b/be-python-fast-api/files/chart/templates/_affinity.tpl @@ -0,0 +1,51 @@ +{{/* +Part of the ODS helm tpl library + +Version: 1.0 +*/}} + + +{{/* +Pod affinity/anti-affinity (soft) + +Usage: Include where needed, e.g. +```` +apiVersion: apps/v1 +kind: Deployment +spec: + template: + spec: + affinity: + podAntiAffinity: {{- include "common.affinities.pods.soft" . | nindent 10}} +```` +*/}} +{{- define "common.affinities.pods.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchLabels: {{- include "common.matchLabels" . | nindent 10 }} + topologyKey: "kubernetes.io/hostname" +{{- end -}} + +{{/* +Pod affinity/anti-affinity (hard) + +Usage: Include where needed, e.g. +```` +apiVersion: apps/v1 +kind: Deployment +spec: + template: + spec: + affinity: + podAntiAffinity: {{- include "common.affinities.pods.hard" . | nindent 10}} +```` +*/}} +{{- define "common.affinities.pods.hard" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- include "common.matchLabels" . | nindent 10 }} + topologyKey: "kubernetes.io/hostname" +{{- end -}} diff --git a/be-python-fast-api/files/chart/templates/_helpers.tpl b/be-python-fast-api/files/chart/templates/_helpers.tpl new file mode 100644 index 000000000..7ba5edc27 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/_image.tpl b/be-python-fast-api/files/chart/templates/_image.tpl new file mode 100644 index 000000000..1a527617b --- /dev/null +++ b/be-python-fast-api/files/chart/templates/_image.tpl @@ -0,0 +1,19 @@ + +{{/* +Part of the ODS helm tpl library + +Version: 1.0 +*/}} + +{{/* +Create an image name from the registry, image path, name and tag. +.Values.registry, .Values.imageNamespace, .Values.componentId and .Values.imageTag are injected by the ODS pipeline on deployment. +If not set, values from .Values.image.registry, .Values.image.path, .Values.image.name and .Values.image.tag are used. +*/}} +{{- define "image.fullname" -}} +{{- if (or .Values.registry .Values.image.registry) }} +{{- printf "%s/%s/%s:%s" (or .Values.registry .Values.image.registry) (or .Values.imageNamespace .Values.image.path) (or .Values.componentId .Values.image.name) (or .Values.imageTag .Values.image.tag ) -}} +{{- else }} +{{- printf "%s/%s:%s" (or .Values.imageNamespace .Values.image.path) (or .Values.componentId .Values.image.name) (or .Values.imageTag .Values.image.tag ) -}} +{{- end }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/_labels.tpl b/be-python-fast-api/files/chart/templates/_labels.tpl new file mode 100644 index 000000000..6a222c379 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/_labels.tpl @@ -0,0 +1,11 @@ +{{/* +Part of the ODS helm tpl library + +Version: 1.0 +*/}} + + +{{- define "common.matchLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/deployment.yaml b/be-python-fast-api/files/chart/templates/deployment.yaml new file mode 100644 index 000000000..03ddf70e8 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "chart.fullname" . }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + strategy: + {{- toYaml .Values.deploymentStrategy | nindent 4 }} + selector: + matchLabels: + {{- include "chart.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "chart.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "chart.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + # Priority is on Values from CICD jenkins injected Helm values, if not then use values from values.yaml + image: {{ include "image.fullname" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + {{- toYaml .Values.probes.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.probes.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + + affinity: + {{- with .Values.affinity }} + {{- toYaml .Values.affinity | nindent 8 }} + {{- end }} + {{- if eq .Values.podAntiAffinity "soft" }} + podAntiAffinity: {{- include "common.affinities.pods.soft" . | nindent 10}} + {{- end }} + {{- if eq .Values.podAntiAffinity "hard" }} + podAntiAffinity: {{- include "common.affinities.pods.hard" . | nindent 10}} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/be-python-fast-api/files/chart/templates/hpa.yaml b/be-python-fast-api/files/chart/templates/hpa.yaml new file mode 100644 index 000000000..58170e998 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/hpa.yaml @@ -0,0 +1,23 @@ +{{- if .Values.autoscaling.enabled -}} +{{- $fullName := include "chart.fullname" . -}} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullName }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullName }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + type: Utilization +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/ingress.yaml b/be-python-fast-api/files/chart/templates/ingress.yaml new file mode 100644 index 000000000..a2c5cb5c4 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/ingress.yaml @@ -0,0 +1,51 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "chart.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- if .Values.ingress.router }} + router: {{ .Values.ingress.router }} + {{- end }} + {{- include "chart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- else }} + tls: + - {} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if .pathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/service.yaml b/be-python-fast-api/files/chart/templates/service.yaml new file mode 100644 index 000000000..4583f232e --- /dev/null +++ b/be-python-fast-api/files/chart/templates/service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.service.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "chart.fullname" . }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "chart.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/serviceaccount.yaml b/be-python-fast-api/files/chart/templates/serviceaccount.yaml new file mode 100644 index 000000000..1df935010 --- /dev/null +++ b/be-python-fast-api/files/chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "chart.serviceAccountName" . }} + labels: + {{- include "chart.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/be-python-fast-api/files/chart/templates/tests/test-connection.yaml b/be-python-fast-api/files/chart/templates/tests/test-connection.yaml new file mode 100644 index 000000000..2ad42849f --- /dev/null +++ b/be-python-fast-api/files/chart/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "chart.fullname" . }}-test-connection" + labels: + {{- include "chart.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: public.ecr.aws/docker/library/busybox + command: ['wget'] + args: ['{{ include "chart.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/be-python-fast-api/files/chart/values.dev.yaml b/be-python-fast-api/files/chart/values.dev.yaml new file mode 100644 index 000000000..99520c495 --- /dev/null +++ b/be-python-fast-api/files/chart/values.dev.yaml @@ -0,0 +1 @@ +# This file is used to override the default values in the chart/values.yaml file for deployment in 'dev' environment \ No newline at end of file diff --git a/be-python-fast-api/files/chart/values.prod.yaml b/be-python-fast-api/files/chart/values.prod.yaml new file mode 100644 index 000000000..3fd11896a --- /dev/null +++ b/be-python-fast-api/files/chart/values.prod.yaml @@ -0,0 +1,3 @@ +# This file is used to override the default values in the chart/values.yaml file for deployment in 'prod' environment + +replicaCount: 2 \ No newline at end of file diff --git a/be-python-fast-api/files/chart/values.schema.json b/be-python-fast-api/files/chart/values.schema.json new file mode 100644 index 000000000..051b56aa1 --- /dev/null +++ b/be-python-fast-api/files/chart/values.schema.json @@ -0,0 +1,552 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "additionalProperties": true, + "properties": { + "replicaCount": { + "description": "Number of replicas to deploy", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "imagePullSecrets": { + "description": "List of image pull secrets", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "nameOverride": { + "description": "Override the name of the chart", + "type": "string", + "default": "" + }, + "fullnameOverride": { + "description": "Override the full name of the chart", + "type": "string", + "default": "" + }, + "image": { + "description": "Container image to deploy", + "type": "object", + "additionalProperties": false, + "properties": { + "registry": { + "description": "Image registry", + "type": "string", + "default": "public.ecr.aws" + }, + "path": { + "description": "Image path", + "type": "string", + "default": "nginx" + }, + "name": { + "description": "Image name", + "type": "string", + "default": "nginx-unprivileged" + }, + "tag": { + "description": "Image tag", + "type": "string", + "default": "alpine-slim" + }, + "pullPolicy": { + "description": "Image pull policy", + "type": "string", + "default": "IfNotPresent" + } + } + }, + "ingress": { + "description": "Ingress configuration for the Helm chart", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "Enable ingress", + "type": "boolean", + "default": false + }, + "className": { + "description": "Ingress class name", + "type": "string", + "default": "openshift-default" + }, + "annotations": { + "description": "Annotations for the ingress", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "hosts": { + "description": "List of ingress hosts", + "type": "array", + "items": { + "type": "object", + "properties": { + "host": { + "description": "Hostname", + "type": "string" + }, + "paths": { + "description": "Paths for the host", + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "description": "Path", + "type": "string" + }, + "pathType": { + "description": "Path type", + "type": "string", + "enum": ["ImplementationSpecific", "Exact", "Prefix"] + } + } + } + } + } + } + }, + "tls": { + "description": "TLS configuration", + "type": "array", + "items": { + "type": "object", + "properties": { + "hosts": { + "description": "List of TLS hosts", + "type": "array", + "items": { + "type": "string" + } + }, + "secretName": { + "description": "Secret name for TLS", + "type": "string" + } + } + } + } + } + }, + "service": { + "description": "Service configuration", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "Enable service", + "type": "boolean", + "default": true + }, + "port": { + "description": "Service port", + "type": "integer", + "default": 8080 + }, + "type": { + "description": "Service type", + "type": "string", + "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"], + "default": "ClusterIP" + } + } + }, + "deploymentStrategy": { + "description": "Deployment strategy configuration", + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "description": "Deployment strategy type", + "type": "string", + "default": "RollingUpdate" + }, + "rollingUpdate": { + "description": "Rolling update configuration", + "type": "object", + "properties": { + "maxUnavailable": { + "description": "Maximum unavailable pods during update", + "type": "string", + "default": "0%" + }, + "maxSurge": { + "description": "Maximum surge pods during update", + "type": "string", + "default": "50%" + } + } + } + } + }, + "probes": { + "description": "Probes configuration", + "type": "object", + "additionalProperties": false, + "properties": { + "livenessProbe": { + "description": "Liveness probe configuration", + "type": "object", + "properties": { + "exec": { + "description": "Exec probe configuration", + "type": "object", + "properties": { + "command": { + "description": "Command to execute", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tcpSocket": { + "description": "TCP socket probe configuration", + "type": "object", + "properties": { + "port": { + "description": "Port to probe", + "type": "integer" + } + } + }, + "failureThreshold": { + "description": "Failure threshold", + "type": "integer", + "default": 3 + }, + "httpGet": { + "description": "HTTP GET configuration for liveness probe", + "type": "object", + "properties": { + "path": { + "description": "Path to probe", + "type": "string", + "default": "/" + }, + "port": { + "description": "Port to probe", + "type": "integer", + "default": 8080 + }, + "scheme": { + "description": "Scheme to use", + "type": "string", + "default": "HTTP" + } + } + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds", + "type": "integer", + "default": 5 + }, + "periodSeconds": { + "description": "Period in seconds", + "type": "integer", + "default": 10 + }, + "successThreshold": { + "description": "Success threshold", + "type": "integer", + "default": 1 + }, + "timeoutSeconds": { + "description": "Timeout in seconds", + "type": "integer", + "default": 3 + } + } + }, + "readinessProbe": { + "description": "Readiness probe configuration", + "type": "object", + "properties": { + "exec": { + "description": "Exec probe configuration", + "type": "object", + "properties": { + "command": { + "description": "Command to execute", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tcpSocket": { + "description": "TCP socket probe configuration", + "type": "object", + "properties": { + "port": { + "description": "Port to probe", + "type": "integer" + } + } + }, + "failureThreshold": { + "description": "Failure threshold", + "type": "integer", + "default": 3 + }, + "httpGet": { + "description": "HTTP GET configuration for liveness probe", + "type": "object", + "properties": { + "path": { + "description": "Path to probe", + "type": "string", + "default": "/" + }, + "port": { + "description": "Port to probe", + "type": "integer", + "default": 8080 + }, + "scheme": { + "description": "Scheme to use", + "type": "string", + "default": "HTTP" + } + } + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds", + "type": "integer", + "default": 5 + }, + "periodSeconds": { + "description": "Period in seconds", + "type": "integer", + "default": 10 + }, + "successThreshold": { + "description": "Success threshold", + "type": "integer", + "default": 1 + }, + "timeoutSeconds": { + "description": "Timeout in seconds", + "type": "integer", + "default": 3 + } + } + } + } + }, + "serviceAccount": { + "description": "Service account configuration", + "type": "object", + "additionalProperties": false, + "properties": { + "create": { + "description": "Create service account", + "type": "boolean", + "default": false + }, + "annotations": { + "description": "Annotations for the service account", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "description": "Name of the service account", + "type": "string" + } + } + }, + "podAnnotations": { + "description": "Annotations for the pod", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "podSecurityContext": { + "description": "Pod security context", + "type": "object", + "additionalProperties": false, + "properties": { + "fsGroup": { + "description": "Filesystem group", + "type": "integer" + } + } + }, + "securityContext": { + "description": "Container security context", + "type": "object", + "additionalProperties": false, + "properties": { + "capabilities": { + "description": "Security capabilities", + "type": "object", + "properties": { + "drop": { + "description": "Capabilities to drop", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "readOnlyRootFilesystem": { + "description": "Read-only root filesystem", + "type": "boolean" + }, + "runAsNonRoot": { + "description": "Run as non-root user", + "type": "boolean" + }, + "runAsUser": { + "description": "User ID to run as", + "type": "integer" + } + } + }, + "resources": { + "description": "Resource requests and limits", + "type": "object", + "additionalProperties": false, + "properties": { + "limits": { + "description": "Resource limits", + "type": "object", + "properties": { + "cpu": { + "description": "CPU limit", + "type": "string", + "default": "100m" + }, + "memory": { + "description": "Memory limit", + "type": "string", + "default": "128Mi" + } + } + }, + "requests": { + "description": "Resource requests", + "type": "object", + "properties": { + "cpu": { + "description": "CPU request", + "type": "string", + "default": "50m" + }, + "memory": { + "description": "Memory request", + "type": "string", + "default": "64Mi" + } + } + } + } + }, + "autoscaling": { + "description": "Autoscaling configuration", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "description": "Enable autoscaling", + "type": "boolean", + "default": false + }, + "minReplicas": { + "description": "Minimum number of replicas", + "type": "integer", + "default": 1 + }, + "maxReplicas": { + "description": "Maximum number of replicas", + "type": "integer", + "default": 100 + }, + "targetCPUUtilizationPercentage": { + "description": "Target CPU utilization percentage", + "type": "integer", + "default": 80 + }, + "targetMemoryUtilizationPercentage": { + "description": "Target memory utilization percentage", + "type": "integer" + } + } + }, + "nodeSelector": { + "description": "Node selector for pod assignment", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "tolerations": { + "description": "Tolerations for pod assignment", + "type": "array", + "items": { + "type": "object" + } + }, + "affinity": { + "description": "Affinity rules for pod assignment", + "type": "object" + }, + "podAntiAffinity": { + "description": "Pod anti-affinity rules", + "type": ["string", "null"], + "default": "soft", + "enum": ["soft", "hard", null] + }, + "registry": { + "description": "Registry configuration - Injected by ODS pipeline", + "type": "string" + }, + "componentId": { + "description": "Component ID - Injected by ODS pipeline", + "type": "string" + }, + "imageTag": { + "description": "Image tag - Injected by ODS pipeline", + "type": "string" + }, + "imageNamespace": { + "description": "Image namespace", + "type": "string" + }, + "global": { + "description": "Global configuration - Injected by ODS pipeline", + "type": "object", + "additionalProperties": true, + "properties": { + "imageNamespace": { + "description": "Image namespace - Injected by ODS pipeline", + "type": "string" + }, + "registry": { + "description": "Registry configuration - Injected by ODS pipeline", + "type": "string" + }, + "componentId": { + "description": "Component ID - Injected by ODS pipeline", + "type": "string" + }, + "imageTag": { + "description": "Image tag - Injected by ODS pipeline", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/be-python-fast-api/files/chart/values.test.yaml b/be-python-fast-api/files/chart/values.test.yaml new file mode 100644 index 000000000..8c6f855da --- /dev/null +++ b/be-python-fast-api/files/chart/values.test.yaml @@ -0,0 +1,3 @@ +# This file is used to override the default values in the chart/values.yaml file for deployment in 'test' environment + +replicaCount: 2 \ No newline at end of file diff --git a/be-python-fast-api/files/chart/values.yaml b/be-python-fast-api/files/chart/values.yaml new file mode 100644 index 000000000..59b7de980 --- /dev/null +++ b/be-python-fast-api/files/chart/values.yaml @@ -0,0 +1,131 @@ +## Default values for chart. +## This is a YAML-formatted file. Intendation matters! +## Comments are prefixed with two hashes (##), examples are commented with one hash (#) +## Declare variables to be passed into your templates. + +## The number of replicas to deploy. +## For high availability use more than 1 replica +## Before enabling check the official ODS documentation about replicate support: https://www.opendevstack.org/ods-documentation/opendevstack/latest/jenkins-shared-library/orchestration-pipeline.html#_known_limitations +replicaCount: 1 + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# NOTE: By default image Values are injected from CICD Jenkins pipeline, values defined here are used when not being on CICD Jenkins pipeline context. +# Default values here are provided in case one needs to use the chart without CICD (i.e.: testing the chart). +image: + # registry: "public.ecr.aws" + # path: "nginx" + # name: "nginx-unprivileged" + # tag: "alpine-slim" + # see https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy + pullPolicy: IfNotPresent + +## Prefer using ingress over openshift routes +ingress: + enabled: false + className: 'openshift-default' + # router: external + annotations: + ## adjust openshift timeouts (default is 60s) + haproxy.router.openshift.io/timeout: 300s + haproxy.router.openshift.io/timeout-tunnel: 300s + ## e.g. add cert-manager support by annotating the ingress https://cert-manager.io/docs/usage/ingress/ + ## ask in your company for good defaults + + hosts: [] # When defining a host you must define also a path + # - host: yourapp.yourdomain.com + # paths: + # - path: / + # pathType: Prefix + tls: [] # If `tls` is left empty then the default OpenShift TLS config will be loaded (i.e.: TLS edge termination with HTTP redirect to HTTPS) + # - secretName: chart-example-tls + # hosts: + # - yourapp.yourdomain.com + +service: + enabled: true + port: 8080 + type: ClusterIP + +# There are two types of strategy: `Recreate` and `RollingUpdate` +# Please refer to https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy +deploymentStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0% + maxSurge: 50% + +probes: + livenessProbe: + failureThreshold: 3 + httpGet: + path: "/health" + port: 8080 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + + readinessProbe: + failureThreshold: 1 + httpGet: + path: "/health" + port: 8080 + scheme: HTTP + initialDelaySeconds: 3 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 3 + +serviceAccount: + # Specifies whether a service account should be created + create: false + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + # name: "default" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: + limits: + cpu: 100m + memory: 64Mi + ephemeral-storage: 1Mi + requests: + cpu: 10m + memory: 32Mi + ephemeral-storage: 1Mi + +## Before enabling check the official ODS documentation about replicate support: https://www.opendevstack.org/ods-documentation/opendevstack/latest/jenkins-shared-library/orchestration-pipeline.html#_known_limitations +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 1 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +## PodAntiAffinity is a way to control that not all of your pods are scheduled +## onto the same node. +## possible values: soft, hard, null +podAntiAffinity: soft diff --git a/be-python-flask/files/docker/Dockerfile b/be-python-fast-api/files/docker/Dockerfile similarity index 100% rename from be-python-flask/files/docker/Dockerfile rename to be-python-fast-api/files/docker/Dockerfile diff --git a/be-python-fast-api/files/docker/run.sh b/be-python-fast-api/files/docker/run.sh new file mode 100644 index 000000000..fb2e288be --- /dev/null +++ b/be-python-fast-api/files/docker/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +exec uvicorn main:app --host 0.0.0.0 --port 8080 --no-access-log diff --git a/be-python-fast-api/files/metadata.yml b/be-python-fast-api/files/metadata.yml new file mode 100644 index 000000000..3ac260318 --- /dev/null +++ b/be-python-fast-api/files/metadata.yml @@ -0,0 +1,6 @@ +--- +name: FastAPI +description: "FastAPI is a modern, high-performance web framework for building APIs with Python. Technologies: FastAPI 0.115, Python 3.12, Uvicorn" +supplier: https://fastapi.tiangolo.com/ +version: 4.x +type: ods diff --git a/be-python-flask/files/mypy.ini b/be-python-fast-api/files/mypy.ini similarity index 82% rename from be-python-flask/files/mypy.ini rename to be-python-fast-api/files/mypy.ini index 198882a70..b839a706e 100644 --- a/be-python-flask/files/mypy.ini +++ b/be-python-fast-api/files/mypy.ini @@ -18,8 +18,3 @@ warn_unused_ignores = True warn_return_any = False no_implicit_reexport = False -[mypy-flask_wtf.*] -ignore_missing_imports = True - -; [mypy-pytest.*] -; ignore_missing_imports = True diff --git a/be-python-flask/files/release-manager.yml b/be-python-fast-api/files/release-manager.yml similarity index 100% rename from be-python-flask/files/release-manager.yml rename to be-python-fast-api/files/release-manager.yml diff --git a/be-python-fast-api/files/requirements.txt b/be-python-fast-api/files/requirements.txt new file mode 100644 index 000000000..792449289 --- /dev/null +++ b/be-python-fast-api/files/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 diff --git a/be-python-fast-api/files/src/main.py b/be-python-fast-api/files/src/main.py new file mode 100644 index 000000000..57cfe425a --- /dev/null +++ b/be-python-fast-api/files/src/main.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get('/') +def hello_world() -> dict: + return {'msg': 'hello world!'} + + +@app.get('/health') +def health() -> dict: + return {'status': 'ok'} + + +# local development ($ python src/main.py) +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8080) diff --git a/be-python-flask/files/tests/__init__.py b/be-python-fast-api/files/tests/__init__.py similarity index 100% rename from be-python-flask/files/tests/__init__.py rename to be-python-fast-api/files/tests/__init__.py diff --git a/be-python-fast-api/files/tests/main_test.py b/be-python-fast-api/files/tests/main_test.py new file mode 100644 index 000000000..5b5411a5a --- /dev/null +++ b/be-python-fast-api/files/tests/main_test.py @@ -0,0 +1,18 @@ +from fastapi.testclient import TestClient +import main + +client = TestClient(main.app) + + +def test_main_base_endpoint_should_return_hello_world(): + response = client.get('/') + + assert response.status_code == 200 + assert response.json()['msg'] == 'hello world!' + + +def test_health_endpoint_should_return_ok(): + response = client.get('/health') + + assert response.status_code == 200 + assert response.json()['status'] == 'ok' diff --git a/be-python-flask/files/tests_requirements.txt b/be-python-fast-api/files/tests_requirements.txt similarity index 85% rename from be-python-flask/files/tests_requirements.txt rename to be-python-fast-api/files/tests_requirements.txt index 97598f743..1226e2a9b 100644 --- a/be-python-flask/files/tests_requirements.txt +++ b/be-python-fast-api/files/tests_requirements.txt @@ -4,3 +4,4 @@ mypy==1.11.1 flake8==7.1.1 pytest==8.3.2 pytest-cov==5.0.0 +httpx==0.28.0 diff --git a/be-python-flask/ocp.env b/be-python-fast-api/ocp.env similarity index 100% rename from be-python-flask/ocp.env rename to be-python-fast-api/ocp.env diff --git a/be-python-flask/sonar-project.properties.template b/be-python-fast-api/sonar-project.properties.template similarity index 100% rename from be-python-flask/sonar-project.properties.template rename to be-python-fast-api/sonar-project.properties.template diff --git a/be-python-fast-api/testdata/functional/api/health-response.json b/be-python-fast-api/testdata/functional/api/health-response.json new file mode 100644 index 000000000..7c137ce8a --- /dev/null +++ b/be-python-fast-api/testdata/functional/api/health-response.json @@ -0,0 +1 @@ +{"status": "ok"} diff --git a/be-python-flask/testdata/functional/integration/smoke_test.sh b/be-python-fast-api/testdata/functional/integration/smoke_test.sh similarity index 100% rename from be-python-flask/testdata/functional/integration/smoke_test.sh rename to be-python-fast-api/testdata/functional/integration/smoke_test.sh diff --git a/be-python-flask/testdata/golden/jenkins-build-stages.json b/be-python-fast-api/testdata/golden/jenkins-build-stages.json similarity index 100% rename from be-python-flask/testdata/golden/jenkins-build-stages.json rename to be-python-fast-api/testdata/golden/jenkins-build-stages.json diff --git a/be-python-flask/testdata/golden/jenkins-provision-stages.json b/be-python-fast-api/testdata/golden/jenkins-provision-stages.json similarity index 100% rename from be-python-flask/testdata/golden/jenkins-provision-stages.json rename to be-python-fast-api/testdata/golden/jenkins-provision-stages.json diff --git a/be-python-fast-api/testdata/steps.yml b/be-python-fast-api/testdata/steps.yml new file mode 100644 index 000000000..6f57f4684 --- /dev/null +++ b/be-python-fast-api/testdata/steps.yml @@ -0,0 +1,98 @@ +componentID: python-fastapi-iq-test +steps: + - type: provision + provisionParams: + verify: + jenkinsStages: golden/jenkins-provision-stages.json + + - type: build + buildParams: + verify: + jenkinsStages: golden/jenkins-build-stages.json + runAttachments: + - sonarqube-report-{{.ProjectID}}-{{.ComponentID}}.pdf + testResults: 1 + openShiftResources: + imageStreams: + - "{{.ComponentID}}" + services: + - "{{.ComponentID}}" + deployments: + - "{{.ComponentID}}" + + # Step 3: Wait for Helm deployment to complete + - type: wait + description: Wait for FastAPI deployment to be ready + waitParams: + condition: deployment-complete + resource: "deployment/{{.ComponentID}}" + namespace: "{{.ProjectID}}-dev" + timeout: "5m" + interval: "5s" + + # Step 4: Wait for pod to be ready + - type: wait + description: Wait for application pod to be ready + waitParams: + condition: pod-ready + resource: "-l app.kubernetes.io/name={{.ComponentID}}" + namespace: "{{.ProjectID}}-dev" + timeout: "3m" + interval: "5s" + + # Step 5: Inspect container logs for successful uvicorn startup + - type: inspect + description: Verify uvicorn started successfully + inspectParams: + resource: "deployment/{{.ComponentID}}" + namespace: "{{.ProjectID}}-dev" + checks: + logs: + contains: + - "Started server process" + - "Waiting for application startup" + - "Application startup complete" + - "Uvicorn running on" + notContains: + - "ERROR" + - "fatal error" + + # Step 6: Expose service for testing + - type: expose-service + description: Make FastAPI service accessible for tests + exposeServiceParams: + services: + - serviceName: "{{.ComponentID}}" + namespace: "{{.ProjectID}}-dev" + port: "8080" + + # Step 7: Test health endpoint with retry + - type: http + description: Verify health endpoint returns 200 + retry: + attempts: 10 + delay: "5s" + onlyTransient: true + httpParams: + url: "http://{{.ComponentID}}.{{.ProjectID}}-dev.svc.cluster.local:8080/health" + method: GET + expectedStatus: 200 + expectedBody: "functional/api/health-response.json" + timeout: 30 + + # Step 8: Test root endpoint + - type: http + description: Verify root endpoint returns hello world + httpParams: + url: "http://{{.ComponentID}}.{{.ProjectID}}-dev.svc.cluster.local:8080/" + method: GET + expectedStatus: 200 + timeout: 30 + + # Step 9: Run integration smoke tests + - type: run + description: Run integration smoke tests + runParams: + file: "functional/integration/smoke_test.sh" + services: + app: "{{.ComponentID}}" diff --git a/be-python-flask/.gitignore b/be-python-flask/.gitignore deleted file mode 100644 index ff4e12e6a..000000000 --- a/be-python-flask/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -venv/ -__pycache__ -.pytest_cache -tests.xml -coverage.xml -.coverage diff --git a/be-python-flask/README.md b/be-python-flask/README.md deleted file mode 100644 index b1076a67d..000000000 --- a/be-python-flask/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Python Flask Quickstarter (be-python-flask) - -Documentation is located in our [official documentation](https://www.opendevstack.org/ods-documentation/ods-quickstarters/latest/index.html) - -Please update documentation in the [antora page directory](https://github.com/opendevstack/ods-quickstarters/tree/master/docs/modules/ROOT/pages) - -Tested thru [automated tests](../tests/be-python-flask) diff --git a/be-python-flask/files/docker/run.sh b/be-python-flask/files/docker/run.sh deleted file mode 100644 index 497a05217..000000000 --- a/be-python-flask/files/docker/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -exec gunicorn -b :8080 --access-logfile /dev/stdout main:app diff --git a/be-python-flask/files/metadata.yml b/be-python-flask/files/metadata.yml deleted file mode 100644 index 5cbe4d5af..000000000 --- a/be-python-flask/files/metadata.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: Flask -description: "Flask is a micro web framework written in Python. Technologies: Flask 3, Python 3.12" -supplier: https://www.palletsprojects.com/p/flask/ -version: 4.x -type: ods diff --git a/be-python-flask/files/requirements.txt b/be-python-flask/files/requirements.txt deleted file mode 100644 index e913c34a8..000000000 --- a/be-python-flask/files/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -gunicorn==23.0.0 -flask==3.0.3 -Flask-WTF==1.2.2 diff --git a/be-python-flask/files/src/main.py b/be-python-flask/files/src/main.py deleted file mode 100644 index 7ee5c5952..000000000 --- a/be-python-flask/files/src/main.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -from flask import Flask, jsonify -from flask_wtf import CSRFProtect - -app = Flask(__name__) -csrf: CSRFProtect = CSRFProtect() -csrf.init_app(app) - - -@app.route('/', methods=['GET']) -def hello_world(): - return jsonify({'msg': 'hello world!'}), 200 - - -# local development ($ python src/main.py) -if __name__ == "__main__": - app.run(host="0.0.0.0", port=8080) diff --git a/be-python-flask/files/tests/main_test.py b/be-python-flask/files/tests/main_test.py deleted file mode 100644 index 96cab2e87..000000000 --- a/be-python-flask/files/tests/main_test.py +++ /dev/null @@ -1,11 +0,0 @@ -import main - -app = main.app - -client = app.test_client() - -def test_main_base_endpoint_should_return_hello_world(): - response = client.get('/') - - assert response.status == '200 OK' - assert response.json['msg'] == 'hello world!' diff --git a/be-python-flask/testdata/functional/api/health-response.json b/be-python-flask/testdata/functional/api/health-response.json deleted file mode 100644 index cefd6d74c..000000000 --- a/be-python-flask/testdata/functional/api/health-response.json +++ /dev/null @@ -1 +0,0 @@ -{"msg":"hello world!"} diff --git a/be-python-flask/testdata/steps.yml b/be-python-flask/testdata/steps.yml deleted file mode 100644 index 252e18681..000000000 --- a/be-python-flask/testdata/steps.yml +++ /dev/null @@ -1,78 +0,0 @@ -componentID: python-flask-iq-test -steps: - - type: provision - provisionParams: - verify: - jenkinsStages: golden/jenkins-provision-stages.json - - type: build - buildParams: - verify: - jenkinsStages: golden/jenkins-build-stages.json - runAttachments: - - sonarqube-report-{{.ProjectID}}-{{.ComponentID}}.pdf - testResults: 1 - openShiftResources: - imageTags: - - name: "{{.ComponentID}}" - tag: latest - imageStreams: - - "{{.ComponentID}}" - deploymentConfigs: - - "{{.ComponentID}}" - services: - - "{{.ComponentID}}" - # Step 3: Wait for deployment to be ready - - type: wait - description: Wait for application pod to be ready - waitParams: - condition: pod-ready - resource: "-l app={{.ProjectID}}-{{.ComponentID}}" - namespace: "{{.ProjectID}}-dev" - timeout: "300s" - interval: "5s" - - # Step 4: Verify logs don't contain errors (NEW) - - type: inspect - description: Verify container logs - inspectParams: - resource: "deploymentconfig/{{.ComponentID}}" - namespace: "{{.ProjectID}}-dev" - checks: - logs: - contains: - - "Starting gunicorn 23.0.0" - - "Listening at: http://0.0.0.0:8080" - - "Using worker: sync" - - "Booting worker with pid:" - notContains: - - "panic:" - - "fatal error" - - "ERROR" - - # Step 5: Expose services - - type: "expose-service" - description: "Expose service with defaults" - exposeServiceParams: - services: - - serviceName: "{{.ComponentID}}" - - # Step 6: Test health endpoint - - type: http - description: Verify health endpoint returns 200 - httpParams: - url: "http://{{.ComponentID}}.{{.ProjectID}}-dev.svc.cluster.local:8080/" - method: GET - expectedStatus: 200 - expectedBody: "functional/api/health-response.json" - timeout: 30 - retry: - attempts: 10 - delay: "5s" - - # Step 7: Run integration smoke tests - - type: run - description: Run integration smoke tests - runParams: - file: "functional/integration/smoke_test.sh" - services: - app: "{{.ComponentID}}"