diff --git a/.github/workflows/gitea-ci.yml b/.github/workflows/gitea-ci.yml new file mode 100644 index 00000000..efdac73b --- /dev/null +++ b/.github/workflows/gitea-ci.yml @@ -0,0 +1,185 @@ +name: Gitea CI + +on: + pull_request: + paths: + - 'applications/gitea/charts/**' + - 'applications/gitea/tests/**' + - 'applications/gitea/Makefile' + - '.github/workflows/gitea-ci.yml' + push: + branches: + - main + paths: + - 'applications/gitea/charts/**' + - 'applications/gitea/tests/**' + - 'applications/gitea/Makefile' + - '.github/workflows/gitea-ci.yml' + +jobs: + lint-and-template: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: applications/gitea + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4.3.0 + with: + version: v3.13.3 + + - name: Add Helm repositories + run: make add-helm-repositories + + - name: Update dependencies + run: make update-dependencies + + - name: Helm lint + run: helm lint ./charts/gitea + + - name: Helm template (default values) + run: helm template gitea ./charts/gitea > /dev/null + + - name: Helm template (CI test values) + run: helm template gitea ./charts/gitea -f tests/helm/ci-values.yaml > /dev/null + + helm-install-test: + runs-on: ubuntu-22.04 + needs: [lint-and-template] + defaults: + run: + working-directory: applications/gitea + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4.3.0 + with: + version: v3.13.3 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Create cluster + id: create-cluster + uses: replicatedhq/replicated-actions/create-cluster@v1.17.0 + with: + api-token: ${{ secrets.REPLICATED_PLATFORM_EXAMPLES_TOKEN }} + kubernetes-distribution: k3s + kubernetes-version: "1.32" + cluster-name: gitea-ci-${{ github.run_id }} + disk: 50 + nodes: 1 + ttl: 1h + export-kubeconfig: true + + - name: Install cert-manager + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + helm repo add jetstack https://charts.jetstack.io + helm repo update jetstack + helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager --create-namespace \ + --set crds.enabled=true \ + --wait --timeout 5m + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Install CloudNativePG operator + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + helm repo add cnpg https://cloudnative-pg.github.io/charts + helm repo update cnpg + helm install cnpg cnpg/cloudnative-pg \ + --namespace cnpg-system --create-namespace \ + --wait --timeout 5m + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Install Gitea chart + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + make add-helm-repositories + make update-dependencies + helm install gitea ./charts/gitea \ + -f tests/helm/ci-values.yaml \ + --wait --timeout 10m + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Wait for pods + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + echo "Waiting for Gitea deployment..." + kubectl wait --for=condition=Available deployment -l app.kubernetes.io/name=gitea \ + --timeout=300s + + echo "Waiting for CNPG postgres cluster..." + kubectl wait --for=condition=Ready pod -l cnpg.io/cluster=gitea-postgres \ + --timeout=300s + + echo "Waiting for Valkey..." + kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=valkey \ + --timeout=120s + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Run smoke tests + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + python -m venv ./venv + source ./venv/bin/activate + pip install -r tests/requirements.txt + python tests/smoke_test.py --release gitea --namespace default + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Debug output + if: failure() + run: | + KUBECONFIG_FILE="/tmp/kubeconfig-${{ github.run_id }}" + echo "$KUBECONFIG" > "$KUBECONFIG_FILE" + export KUBECONFIG="$KUBECONFIG_FILE" + + echo "=== Pods ===" + kubectl get pods -A + echo "=== Services ===" + kubectl get svc + echo "=== Events ===" + kubectl get events --sort-by='.lastTimestamp' | tail -40 + echo "=== Gitea pod logs ===" + kubectl logs -l app.kubernetes.io/name=gitea --tail=50 || true + echo "=== CNPG cluster status ===" + kubectl get clusters.postgresql.cnpg.io -o yaml || true + echo "=== Postgres pod logs ===" + kubectl logs -l cnpg.io/cluster=gitea-postgres --tail=30 || true + env: + KUBECONFIG: ${{ steps.create-cluster.outputs.cluster-kubeconfig }} + + - name: Remove cluster + uses: replicatedhq/replicated-actions/remove-cluster@v1.17.0 + if: ${{ always() && steps.create-cluster.outputs.cluster-id != '' }} + with: + api-token: ${{ secrets.REPLICATED_PLATFORM_EXAMPLES_TOKEN }} + cluster-id: ${{ steps.create-cluster.outputs.cluster-id }} diff --git a/applications/gitea/Makefile b/applications/gitea/Makefile index 9a26147f..6e43fd13 100644 --- a/applications/gitea/Makefile +++ b/applications/gitea/Makefile @@ -6,7 +6,7 @@ ARGS = $(filter-out $@,$(MAKECMDGOALS)) @: SHELL := /bin/bash -.SHELLFLAGS = -x +u +.SHELLFLAGS = -x +u -c # Define the base path to your Helm charts directory HELM_CHARTS_DIR = ./charts @@ -70,7 +70,7 @@ add-helm-repositories: @for chart_file in $(HELM_CHARTS_DIR)/*/Chart.yaml; do \ echo "Processing $$chart_file"; \ repo_name=$$(grep '^name:' $$chart_file | awk '{print $$2}'); \ - grep 'dependencies:' -A 10 $$chart_file | grep 'repository:' | awk '{print $$2}' | while read repo; do \ + grep 'dependencies:' -A 10 $$chart_file | grep 'repository:' | awk '{print $$2}' | tr -d '"' | while read repo; do \ if ! helm repo list | grep -q "^$$repo_name[[:space:]]"; then \ echo "Adding Helm repo $$repo_name from $$repo"; \ helm repo add $$repo_name $$repo || true; \ @@ -85,3 +85,64 @@ release: package-and-update @chart_version=$$(eval $(call get_gitea_chart_version)); \ echo "Creating a new release with Replicated using version $$chart_version"; \ replicated release create --yaml-dir $(KOTS_DIR) --promote Unstable --version "$$chart_version" + + +# --------------------------------------------------------------------------- +# Test targets +# --------------------------------------------------------------------------- + +CI_VALUES = tests/helm/ci-values.yaml +RELEASE_NAME = gitea +NAMESPACE = default + +.PHONY: test-lint +test-lint: add-helm-repositories update-dependencies + helm lint ./charts/gitea + helm template $(RELEASE_NAME) ./charts/gitea > /dev/null + helm template $(RELEASE_NAME) ./charts/gitea -f $(CI_VALUES) > /dev/null + +.PHONY: test-install-operators +test-install-operators: + helm repo add jetstack https://charts.jetstack.io || true + helm repo add cnpg https://cloudnative-pg.github.io/charts || true + helm repo update jetstack cnpg + helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager --create-namespace \ + --set crds.enabled=true \ + --wait --timeout 5m + helm install cnpg cnpg/cloudnative-pg \ + --namespace cnpg-system --create-namespace \ + --wait --timeout 5m + +.PHONY: test-install +test-install: add-helm-repositories update-dependencies + helm install $(RELEASE_NAME) ./charts/gitea \ + -f $(CI_VALUES) \ + --namespace $(NAMESPACE) \ + --wait --timeout 10m + +.PHONY: test-smoke +test-smoke: + python3 -m venv ./venv + ./venv/bin/pip install -r tests/requirements.txt + ./venv/bin/python tests/smoke_test.py \ + --release $(RELEASE_NAME) --namespace $(NAMESPACE) + +.PHONY: test-all +test-all: test-lint test-install-operators test-install test-smoke + +.PHONY: help +help: + @echo "Build targets:" + @echo " package-and-update Package charts and update KOTS versions" + @echo " clean Remove build artifacts" + @echo " update-dependencies Update Helm chart dependencies" + @echo " add-helm-repositories Add required Helm repos" + @echo " release Package and create a Replicated release" + @echo "" + @echo "Test targets:" + @echo " test-lint Lint and template-render the chart" + @echo " test-install-operators Install cert-manager and CloudNativePG" + @echo " test-install Helm install Gitea with CI values" + @echo " test-smoke Run Python smoke tests" + @echo " test-all Full test sequence (lint -> operators -> install -> smoke)" diff --git a/applications/gitea/charts/gitea/Chart.lock b/applications/gitea/charts/gitea/Chart.lock index 5023ad23..7c2c981e 100644 --- a/applications/gitea/charts/gitea/Chart.lock +++ b/applications/gitea/charts/gitea/Chart.lock @@ -1,15 +1,6 @@ dependencies: -- name: postgresql - repository: oci://registry-1.docker.io/bitnamicharts - version: 15.5.20 -- name: postgresql-ha - repository: oci://registry-1.docker.io/bitnamicharts - version: 14.2.16 -- name: redis-cluster - repository: oci://registry-1.docker.io/bitnamicharts - version: 10.3.0 -- name: redis - repository: oci://registry-1.docker.io/bitnamicharts - version: 19.6.4 -digest: sha256:a28c809273f313c482e3f803a0a002c3bb3a0d2090bf6b732d68ecc4710b4732 -generated: "2024-08-03T00:21:16.080925346Z" +- name: valkey + repository: https://valkey.io/valkey-helm/ + version: 0.9.3 +digest: sha256:7379cfb883db0b651cfdda1338ff7d2b20985c58152603784832478ed2449486 +generated: "2026-02-09T15:38:46.097337-05:00" diff --git a/applications/gitea/charts/gitea/Chart.yaml b/applications/gitea/charts/gitea/Chart.yaml index 4fdb5d1e..921bf2b8 100644 --- a/applications/gitea/charts/gitea/Chart.yaml +++ b/applications/gitea/charts/gitea/Chart.yaml @@ -1,22 +1,10 @@ apiVersion: v2 appVersion: 1.22.3 dependencies: -- condition: postgresql.enabled - name: postgresql - repository: oci://registry-1.docker.io/bitnamicharts - version: 15.5.20 -- condition: postgresql-ha.enabled - name: postgresql-ha - repository: oci://registry-1.docker.io/bitnamicharts - version: 14.2.16 -- condition: redis-cluster.enabled - name: redis-cluster - repository: oci://registry-1.docker.io/bitnamicharts - version: 10.3.0 -- condition: redis.enabled - name: redis - repository: oci://registry-1.docker.io/bitnamicharts - version: 19.6.4 +- name: valkey + version: "0.9.3" + repository: "https://valkey.io/valkey-helm/" + condition: valkey.enabled description: Gitea Helm chart for Kubernetes icon: https://gitea.com/assets/img/logo.svg keywords: @@ -45,4 +33,4 @@ sources: - https://github.com/go-gitea/gitea - https://hub.docker.com/r/gitea/gitea/ type: application -version: 10.6.0 +version: 11.0.0 diff --git a/applications/gitea/charts/gitea/templates/_helpers.tpl b/applications/gitea/charts/gitea/templates/_helpers.tpl index 64a5efb6..d04d1fc2 100644 --- a/applications/gitea/charts/gitea/templates/_helpers.tpl +++ b/applications/gitea/charts/gitea/templates/_helpers.tpl @@ -311,7 +311,7 @@ https {{- if not (hasKey .Values.gitea.config.metrics "ENABLED") -}} {{- $_ := set .Values.gitea.config.metrics "ENABLED" .Values.gitea.metrics.enabled -}} {{- end -}} - {{- /* redis queue */ -}} + {{- /* redis/valkey queue */ -}} {{- if or ((index .Values "redis-cluster").enabled) ((index .Values "redis").enabled) -}} {{- $_ := set .Values.gitea.config.queue "TYPE" "redis" -}} {{- $_ := set .Values.gitea.config.queue "CONN_STR" (include "redis.dns" .) -}} @@ -319,6 +319,16 @@ https {{- $_ := set .Values.gitea.config.session "PROVIDER_CONFIG" (include "redis.dns" .) -}} {{- $_ := set .Values.gitea.config.cache "ADAPTER" "redis" -}} {{- $_ := set .Values.gitea.config.cache "HOST" (include "redis.dns" .) -}} + {{- else if .Values.valkey.enabled -}} + {{- $defaultUser := index .Values.valkey.auth.aclUsers "default" -}} + {{- $valkeyHost := printf "%s-valkey" (include "gitea.fullname" .) -}} + {{- $valkeyConnStr := printf "redis://:%s@%s:%v/0?pool_size=100&idle_timeout=180s&" $defaultUser.password $valkeyHost (int .Values.valkey.service.port) -}} + {{- $_ := set .Values.gitea.config.queue "TYPE" "redis" -}} + {{- $_ := set .Values.gitea.config.queue "CONN_STR" $valkeyConnStr -}} + {{- $_ := set .Values.gitea.config.session "PROVIDER" "redis" -}} + {{- $_ := set .Values.gitea.config.session "PROVIDER_CONFIG" $valkeyConnStr -}} + {{- $_ := set .Values.gitea.config.cache "ADAPTER" "redis" -}} + {{- $_ := set .Values.gitea.config.cache "HOST" $valkeyConnStr -}} {{- else -}} {{- if not (get .Values.gitea.config.session "PROVIDER") -}} {{- $_ := set .Values.gitea.config.session "PROVIDER" "memory" -}} @@ -412,6 +422,21 @@ https {{- $_ := set .Values.gitea.config.database "USER" .Values.postgresql.global.postgresql.auth.username -}} {{- $_ := set .Values.gitea.config.database "PASSWD" .Values.postgresql.global.postgresql.auth.password -}} {{- end -}} + {{- if .Values.postgres.embedded.enabled -}} + {{- $_ := set .Values.gitea.config.database "DB_TYPE" "postgres" -}} + {{- if not (.Values.gitea.config.database.HOST) -}} + {{- $_ := set .Values.gitea.config.database "HOST" (printf "%s-postgres-rw:5432" (include "gitea.fullname" .)) -}} + {{- end -}} + {{- if not (.Values.gitea.config.database.NAME) -}} + {{- $_ := set .Values.gitea.config.database "NAME" .Values.postgres.embedded.initdb.database -}} + {{- end -}} + {{- if not (.Values.gitea.config.database.USER) -}} + {{- $_ := set .Values.gitea.config.database "USER" .Values.postgres.auth.username -}} + {{- end -}} + {{- if not (.Values.gitea.config.database.PASSWD) -}} + {{- $_ := set .Values.gitea.config.database "PASSWD" .Values.postgres.auth.password -}} + {{- end -}} + {{- end -}} {{- end -}} {{- define "gitea.init-additional-mounts" -}} diff --git a/applications/gitea/charts/gitea/templates/gitea/postgres-cluster.yaml b/applications/gitea/charts/gitea/templates/gitea/postgres-cluster.yaml new file mode 100644 index 00000000..8f794597 --- /dev/null +++ b/applications/gitea/charts/gitea/templates/gitea/postgres-cluster.yaml @@ -0,0 +1,35 @@ +{{- if .Values.postgres.embedded.enabled -}} +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ include "gitea.fullname" . }}-postgres + labels: + {{- include "gitea.labels" . | nindent 4 }} +spec: + instances: {{ .Values.postgres.embedded.instances }} + imageName: "{{ .Values.postgres.embedded.image.repository }}:{{ .Values.postgres.embedded.image.tag }}" + {{- with .Values.postgres.embedded.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} + bootstrap: + initdb: + database: {{ .Values.postgres.embedded.initdb.database | quote }} + owner: {{ .Values.postgres.embedded.initdb.owner | quote }} + secret: + name: {{ .Values.postgres.auth.existingSecret | default (printf "%s-postgres-initdb-secret" (include "gitea.fullname" .)) }} + storage: + size: {{ .Values.postgres.embedded.storage.size }} + {{- with .Values.postgres.embedded.storage.storageClass }} + storageClassName: {{ . }} + {{- end }} + enableSuperuserAccess: true + primaryUpdateMethod: switchover + primaryUpdateStrategy: unsupervised + {{- if .Values.postgres.embedded.backup.enabled }} + backup: + retentionPolicy: {{ .Values.postgres.embedded.backup.retentionPolicy | quote }} + barmanObjectStore: + {{- toYaml .Values.postgres.embedded.backup.barmanObjectStore | nindent 6 }} + {{- end }} +{{- end -}} diff --git a/applications/gitea/charts/gitea/templates/gitea/postgres-secret.yaml b/applications/gitea/charts/gitea/templates/gitea/postgres-secret.yaml new file mode 100644 index 00000000..70d9baf3 --- /dev/null +++ b/applications/gitea/charts/gitea/templates/gitea/postgres-secret.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.postgres.embedded.enabled (not .Values.postgres.auth.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "gitea.fullname" . }}-postgres-initdb-secret + labels: + {{- include "gitea.labels" . | nindent 4 }} +type: kubernetes.io/basic-auth +stringData: + username: {{ .Values.postgres.auth.username | quote }} + password: {{ .Values.postgres.auth.password | quote }} +{{- end -}} diff --git a/applications/gitea/charts/gitea/templates/tests/test-http-connection.yaml b/applications/gitea/charts/gitea/templates/tests/test-http-connection.yaml index da28ea6c..40b01739 100644 --- a/applications/gitea/charts/gitea/templates/tests/test-http-connection.yaml +++ b/applications/gitea/charts/gitea/templates/tests/test-http-connection.yaml @@ -7,7 +7,7 @@ metadata: labels: {{ include "gitea.labels" . | nindent 4 }} annotations: - "helm.sh/hook": test-success + "helm.sh/hook": test spec: containers: - name: wget diff --git a/applications/gitea/charts/gitea/values.yaml b/applications/gitea/charts/gitea/values.yaml index a108b11b..d00e9031 100644 --- a/applications/gitea/charts/gitea/values.yaml +++ b/applications/gitea/charts/gitea/values.yaml @@ -434,8 +434,8 @@ actions: affinity: {} publish: - repository: bitnami/kubectl - tag: 1.29.0 + repository: registry.k8s.io/kubectl + tag: v1.33.0 pullPolicy: IfNotPresent ttlSecondsAfterFinished: 300 @@ -521,6 +521,8 @@ gitea: server: SSH_PORT: 22 # rootful image SSH_LISTEN_PORT: 2222 # rootless image + database: + DB_TYPE: postgres # # security: # PASSWORD_COMPLEXITY: spec @@ -602,95 +604,66 @@ gitea: successThreshold: 1 failureThreshold: 10 -## @section redis-cluster -## @param redis-cluster.enabled Enable redis cluster -# ⚠️ The redis charts do not work well with special characters in the password (). -# Consider omitting such or open an issue in the Bitnami repo and let us know once this got fixed. -## @param redis-cluster.usePassword Whether to use password authentication -## @param redis-cluster.cluster.nodes Number of redis cluster master nodes -## @param redis-cluster.cluster.replicas Number of redis cluster master node replicas -## @descriptionStart -## Redis cluster and [Redis](#redis) cannot be enabled at the same time. -## @descriptionEnd +## @section Bitnami subcharts (disabled - replaced by CNPG and Valkey) redis-cluster: - enabled: true - usePassword: false - cluster: - nodes: 3 # default: 6 - replicas: 0 # default: 1 - -## @section redis -## @param redis.enabled Enable redis standalone or replicated -## @param redis.architecture Whether to use standalone or replication -# ⚠️ The redis charts do not work well with special characters in the password (). -# Consider omitting such or open an issue in the Bitnami repo and let us know once this got fixed. -## @param redis.global.redis.password Required password -## @param redis.master.count Number of Redis master instances to deploy -## @descriptionStart -## Redis and [Redis cluster](#redis-cluster) cannot be enabled at the same time. -## @descriptionEnd + enabled: false + redis: enabled: false - architecture: standalone - global: - redis: - password: changeme - master: - count: 1 - -## @section PostgreSQL HA -# -## @param postgresql-ha.enabled Enable PostgreSQL HA -## @param postgresql-ha.postgresql.password Password for the `gitea` user (overrides `auth.password`) -## @param postgresql-ha.global.postgresql.database Name for a custom database to create (overrides `auth.database`) -## @param postgresql-ha.global.postgresql.username Name for a custom user to create (overrides `auth.username`) -## @param postgresql-ha.global.postgresql.password Name for a custom password to create (overrides `auth.password`) -## @param postgresql-ha.postgresql.repmgrPassword Repmgr Password -## @param postgresql-ha.postgresql.postgresPassword postgres Password -## @param postgresql-ha.pgpool.adminPassword pgpool adminPassword -## @param postgresql-ha.service.ports.postgresql PostgreSQL service port (overrides `service.ports.postgresql`) -## @param postgresql-ha.persistence.size PVC Storage Request for PostgreSQL HA volume + postgresql-ha: - global: - postgresql: - database: gitea - password: gitea - username: gitea - enabled: true - postgresql: - repmgrPassword: changeme2 - postgresPassword: changeme1 - password: changeme4 - pgpool: - adminPassword: changeme3 - service: - ports: - postgresql: 5432 - persistence: - size: 10Gi + enabled: false -## @section PostgreSQL -# -## @param postgresql.enabled Enable PostgreSQL -## @param postgresql.global.postgresql.auth.password Password for the `gitea` user (overrides `auth.password`) -## @param postgresql.global.postgresql.auth.database Name for a custom database to create (overrides `auth.database`) -## @param postgresql.global.postgresql.auth.username Name for a custom user to create (overrides `auth.username`) -## @param postgresql.global.postgresql.service.ports.postgresql PostgreSQL service port (overrides `service.ports.postgresql`) -## @param postgresql.primary.persistence.size PVC Storage Request for PostgreSQL volume postgresql: enabled: false - global: - postgresql: - auth: - password: gitea - database: gitea - username: gitea - service: - ports: - postgresql: 5432 - primary: - persistence: + +## @section Embedded PostgreSQL via CloudNativePG operator +postgres: + auth: + existingSecret: "" + username: gitea + password: gitea + embedded: + enabled: true + instances: 1 + image: + repository: ghcr.io/cloudnative-pg/postgresql + tag: "15.10" + initdb: + database: gitea + owner: gitea + storage: size: 10Gi + storageClass: "" + resources: {} + # Example: + # requests: + # cpu: 250m + # memory: 512Mi + # limits: + # cpu: "1" + # memory: 1Gi + backup: + enabled: false + retentionPolicy: "30d" + barmanObjectStore: {} + +## @section Valkey cache (Redis-compatible) +valkey: + enabled: true + auth: + enabled: true + aclUsers: + default: + password: "changeme" + permissions: "~* &* +@all" + replica: + enabled: false + dataStorage: + enabled: false + service: + type: ClusterIP + port: 6379 # By default, removed or moved settings that still remain in a user defined values.yaml will cause Helm to fail running the install/update. # Set it to false to skip this basic validation check. diff --git a/applications/gitea/kots/ec.yaml b/applications/gitea/kots/ec.yaml index 3e0d2dac..069bb040 100644 --- a/applications/gitea/kots/ec.yaml +++ b/applications/gitea/kots/ec.yaml @@ -1,17 +1,21 @@ apiVersion: embeddedcluster.replicated.com/v1beta1 kind: Config spec: - version: 2.0.0+k8s-1.30 + version: 2.13.3+k8s-1.33 extensions: helm: repositories: - name: ingress-nginx url: https://kubernetes.github.io/ingress-nginx + - name: cnpg + url: https://cloudnative-pg.github.io/charts + - name: jetstack + url: https://charts.jetstack.io charts: - name: ingress-nginx chartname: ingress-nginx/ingress-nginx namespace: ingress-nginx - version: "4.9.1" + version: "4.14.1" values: | controller: service: @@ -19,3 +23,16 @@ spec: nodePorts: http: 80 https: 443 + - name: cloudnative-pg + chartname: cnpg/cloudnative-pg + namespace: cnpg + version: "0.27.0" + - name: cert-manager + chartname: jetstack/cert-manager + namespace: cert-manager + version: "v1.19.1" + values: | + crds: + enabled: true + prometheus: + enabled: false diff --git a/applications/gitea/kots/gitea-chart.yaml b/applications/gitea/kots/gitea-chart.yaml index ee597cbd..d281de8f 100644 --- a/applications/gitea/kots/gitea-chart.yaml +++ b/applications/gitea/kots/gitea-chart.yaml @@ -5,7 +5,7 @@ metadata: spec: chart: name: gitea - chartVersion: 10.6.0 + chartVersion: 11.0.0 values: gitea: config: @@ -16,4 +16,48 @@ spec: USER: repl{{ ConfigOption "postgres_user" }} PASSWD: repl{{ ConfigOption "postgres_password" }} postgresql-ha: - enabled: repl{{ ConfigOptionEquals "internal_postgres_enabled" "1" }} + enabled: false + postgresql: + enabled: false + redis-cluster: + enabled: false + redis: + enabled: false + postgres: + auth: + username: repl{{ ConfigOption "postgres_user" }} + password: repl{{ ConfigOption "postgres_password" }} + embedded: + enabled: repl{{ ConfigOptionEquals "internal_postgres_enabled" "1" }} + initdb: + database: repl{{ ConfigOption "postgres_db" }} + owner: repl{{ ConfigOption "postgres_user" }} + valkey: + enabled: repl{{ ConfigOptionEquals "internal_valkey_enabled" "1" }} + auth: + enabled: true + aclUsers: + default: + password: repl{{ ConfigOption "valkey_password" }} + optionalValues: + - when: 'repl{{ ConfigOptionEquals "internal_valkey_enabled" "1" }}' + recursiveMerge: true + values: + gitea: + config: + cache: + ADAPTER: redis + HOST: 'redis://:repl{{ ConfigOption "valkey_password" }}@gitea-valkey:6379/0?pool_size=100&idle_timeout=180s&' + session: + PROVIDER: redis + PROVIDER_CONFIG: 'redis://:repl{{ ConfigOption "valkey_password" }}@gitea-valkey:6379/0?pool_size=100&idle_timeout=180s&' + queue: + TYPE: redis + CONN_STR: 'redis://:repl{{ ConfigOption "valkey_password" }}@gitea-valkey:6379/0?pool_size=100&idle_timeout=180s&' + - when: 'repl{{ ConfigOptionEquals "internal_postgres_enabled" "1" }}' + recursiveMerge: true + values: + gitea: + config: + database: + HOST: "gitea-postgres-rw:5432" diff --git a/applications/gitea/kots/kots-app.yaml b/applications/gitea/kots/kots-app.yaml index 286e8932..4e57685f 100644 --- a/applications/gitea/kots/kots-app.yaml +++ b/applications/gitea/kots/kots-app.yaml @@ -7,3 +7,5 @@ spec: allowRollback: false statusInformers: - deployment/kotsadm + - deployment/gitea + - deployment/gitea-valkey diff --git a/applications/gitea/kots/kots-config.yaml b/applications/gitea/kots/kots-config.yaml index a9609270..d65101b8 100644 --- a/applications/gitea/kots/kots-config.yaml +++ b/applications/gitea/kots/kots-config.yaml @@ -12,31 +12,49 @@ spec: - name: internal_postgres_enabled title: Enable Internal Postgres type: bool - default: false + default: true required: true description: Enable Postgres - name: postgres_user title: Postgres User type: text - default: postgres + default: gitea required: true description: The username for the Postgres user - name: postgres_password title: Postgres Password type: password - default: postgres + default: gitea required: true description: Postgres user password secret: true - name: postgres_db title: Postgres Database Name type: text - default: postgres + default: gitea required: true description: The name of the Postgres database - name: postgres_host title: Postgres Database Host type: text - default: postgresql + default: gitea-postgres-rw:5432 required: true description: The host of the Postgres database + # Cache settings + - name: cache_settings + title: Cache Settings + items: + - name: internal_valkey_enabled + title: Enable Internal Valkey Cache + type: bool + default: true + required: true + description: Deploy Valkey (Redis-compatible) for cache, session, and queue + - name: valkey_password + title: Valkey Password + type: password + default: changeme + required: true + description: Password for Valkey authentication + secret: true + when: 'repl{{ ConfigOptionEquals "internal_valkey_enabled" "1" }}' diff --git a/applications/gitea/tests/helm/ci-values.yaml b/applications/gitea/tests/helm/ci-values.yaml new file mode 100644 index 00000000..0c609d31 --- /dev/null +++ b/applications/gitea/tests/helm/ci-values.yaml @@ -0,0 +1,56 @@ +# CI test values for Gitea chart +# Minimal overrides for single-node CI testing +--- +replicaCount: 1 + +ingress: + enabled: false + +persistence: + size: 2Gi + annotations: + helm.sh/resource-policy: "" + +gitea: + admin: + username: test-admin + password: test-password-ci + email: "test@ci.local" + config: + server: + SSH_PORT: 22 + SSH_LISTEN_PORT: 2222 + database: + DB_TYPE: postgres + +actions: + enabled: false + +# Embedded PostgreSQL via CloudNativePG +postgres: + auth: + username: gitea + password: gitea + embedded: + enabled: true + instances: 1 + storage: + size: 2Gi + resources: {} + +# Valkey cache +valkey: + enabled: true + auth: + enabled: true + aclUsers: + default: + password: "ci-test-valkey" + permissions: "~* &* +@all" + replica: + enabled: false + dataStorage: + enabled: false + +# Reduce resource pressure for CI +resources: {} diff --git a/applications/gitea/tests/requirements.txt b/applications/gitea/tests/requirements.txt new file mode 100644 index 00000000..f2293605 --- /dev/null +++ b/applications/gitea/tests/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/applications/gitea/tests/smoke_test.py b/applications/gitea/tests/smoke_test.py new file mode 100644 index 00000000..77853040 --- /dev/null +++ b/applications/gitea/tests/smoke_test.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Smoke tests for the Gitea Helm chart. + +Validates that the three core components are reachable after a Helm install: + - Gitea HTTP (web UI / API on port 3000) + - PostgreSQL via CloudNativePG (port 5432) + - Valkey cache (Redis-compatible, port 6379) + +Usage: + python smoke_test.py [--namespace NAMESPACE] [--release RELEASE] +""" + +import argparse +import json +import logging +import socket +import subprocess +import sys +import time + +import requests + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) +log = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _kubectl(args: list[str], namespace: str | None = None) -> str: + """Run a kubectl command and return stdout.""" + cmd = ["kubectl"] + if namespace: + cmd += ["-n", namespace] + cmd += args + log.debug("Running: %s", " ".join(cmd)) + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return result.stdout.strip() + + +def discover_service( + namespace: str, + label_selector: str, + fallback_name: str, + name_contains: str | None = None, +) -> str: + """Find a service by label selector, falling back to a known name. + + If *name_contains* is given, only services whose name includes that + substring are considered (useful for picking the CNPG ``-rw`` service + out of the ``-r``, ``-ro``, ``-rw`` triple). + """ + try: + out = _kubectl( + ["get", "svc", "-l", label_selector, "-o", "json"], + namespace=namespace, + ) + services = json.loads(out) + items = services.get("items", []) + if name_contains: + items = [i for i in items if name_contains in i["metadata"]["name"]] + if items: + name = items[0]["metadata"]["name"] + log.info("Discovered service %s via labels '%s'", name, label_selector) + return name + except (subprocess.CalledProcessError, json.JSONDecodeError, KeyError) as exc: + log.warning("Label-based discovery failed (%s), using fallback: %s", exc, fallback_name) + return fallback_name + + +def _free_port() -> int: + """Return an available TCP port on localhost.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + + +class PortForward: + """Context manager that runs ``kubectl port-forward`` in the background.""" + + def __init__(self, namespace: str, service: str, remote_port: int): + self.namespace = namespace + self.service = service + self.remote_port = remote_port + self.local_port = _free_port() + self._proc: subprocess.Popen | None = None + + def __enter__(self): + cmd = [ + "kubectl", "-n", self.namespace, + "port-forward", f"svc/{self.service}", + f"{self.local_port}:{self.remote_port}", + ] + log.info("Starting port-forward: %s", " ".join(cmd)) + self._proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ) + # Give the tunnel a moment to establish. + time.sleep(3) + if self._proc.poll() is not None: + stderr = self._proc.stderr.read().decode() if self._proc.stderr else "" + raise RuntimeError(f"port-forward exited immediately: {stderr}") + return self + + def __exit__(self, *_exc): + if self._proc and self._proc.poll() is None: + self._proc.terminate() + self._proc.wait(timeout=5) + + +def tcp_check(host: str, port: int, timeout: float = 5.0) -> bool: + """Return True if a TCP connection to host:port succeeds.""" + try: + with socket.create_connection((host, port), timeout=timeout): + return True + except OSError: + return False + + +def _retry(fn, description: str, retries: int = 10, interval: float = 3.0): + """Retry *fn* until it returns a truthy value or we run out of attempts.""" + for attempt in range(1, retries + 1): + log.info("[%d/%d] %s", attempt, retries, description) + try: + result = fn() + if result: + return result + except Exception as exc: + log.warning(" attempt %d failed: %s", attempt, exc) + if attempt < retries: + time.sleep(interval) + raise RuntimeError(f"All {retries} attempts failed: {description}") + + +# --------------------------------------------------------------------------- +# Component checks +# --------------------------------------------------------------------------- + +def check_gitea(namespace: str, release: str) -> bool: + """Verify Gitea HTTP is responding via /api/v1/version.""" + svc = discover_service( + namespace, + label_selector=f"app.kubernetes.io/name=gitea,app.kubernetes.io/instance={release}", + fallback_name=f"{release}-http", + name_contains="-http", + ) + with PortForward(namespace, svc, 3000) as pf: + def _probe(): + url = f"http://127.0.0.1:{pf.local_port}/api/v1/version" + resp = requests.get(url, timeout=5) + resp.raise_for_status() + data = resp.json() + log.info("Gitea version: %s", data.get("version", "unknown")) + return True + + _retry(_probe, "Checking Gitea HTTP /api/v1/version") + log.info("Gitea HTTP check passed") + return True + + +def check_postgres(namespace: str, release: str) -> bool: + """Verify PostgreSQL (CNPG) is accepting TCP connections on port 5432.""" + svc = discover_service( + namespace, + label_selector=f"cnpg.io/cluster={release}-postgres", + fallback_name=f"{release}-postgres-rw", + name_contains="-rw", + ) + with PortForward(namespace, svc, 5432) as pf: + def _probe(): + return tcp_check("127.0.0.1", pf.local_port) + + _retry(_probe, "Checking PostgreSQL TCP connectivity") + log.info("PostgreSQL check passed") + return True + + +def check_valkey(namespace: str, release: str) -> bool: + """Verify Valkey is accepting TCP connections on port 6379.""" + svc = discover_service( + namespace, + label_selector=f"app.kubernetes.io/name=valkey,app.kubernetes.io/instance={release}", + fallback_name=f"{release}-valkey", + ) + with PortForward(namespace, svc, 6379) as pf: + def _probe(): + return tcp_check("127.0.0.1", pf.local_port) + + _retry(_probe, "Checking Valkey TCP connectivity") + log.info("Valkey check passed") + return True + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser(description="Gitea Helm chart smoke tests") + parser.add_argument("--namespace", default="default", help="Kubernetes namespace") + parser.add_argument("--release", default="gitea", help="Helm release name") + args = parser.parse_args() + + checks = [ + ("Gitea HTTP", check_gitea), + ("PostgreSQL", check_postgres), + ("Valkey", check_valkey), + ] + + failed = [] + for name, fn in checks: + log.info("--- Running check: %s ---", name) + try: + fn(args.namespace, args.release) + except Exception as exc: + log.error("FAIL: %s — %s", name, exc) + failed.append(name) + + print() + if failed: + print(f"FAILED checks: {', '.join(failed)}") + sys.exit(1) + else: + print("All smoke tests passed.") + sys.exit(0) + + +if __name__ == "__main__": + main()