Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-and-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- 3d
- dem
- raster
- pycsw
steps:
- name: Display matrix and index
run: |
Expand Down
26 changes: 26 additions & 0 deletions helm/pycsw/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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/
*.txt
*local.json
*local.yaml
13 changes: 13 additions & 0 deletions helm/pycsw/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v2
name: pycsw
description: Common Helm chart for pycsw service, consumed as a dependency by team deployment charts
type: application
version: 6.6.1
appVersion: 6.6.1
dependencies:
- name: nginx
version: 2.2.1
repository: oci://acrarolibotnonprod.azurecr.io/helm/common
- name: mclabels
version: 1.0.1
repository: oci://acrarolibotnonprod.azurecr.io/helm/infra
107 changes: 107 additions & 0 deletions helm/pycsw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# pycsw common chart

Common Helm chart for the MapColonies pycsw service. It is not deployed directly;
each team consumes it as a dependency from its own deployment repository and injects
its team-specific configuration.

## Consuming the chart

In your team deployment repo, create a thin wrapper chart (see `examples/team-wrapper`
in this repository for a complete working example):

```yaml
# Chart.yaml
dependencies:
- name: pycsw
version: 6.6.1
repository: oci://acrarolibotnonprod.azurecr.io/helm
```

## Team configuration contract

The chart does **not** ship `pycsw.cfg` or `mappings.py` — these are team-specific.
Your wrapper chart must provide a ConfigMap containing both keys and pass its name
via `existingConfigmap` (the value may be a template):

```yaml
# wrapper values.yaml
pycsw:
existingConfigmap: '{{ .Release.Name }}-pycsw-team-config'
```

```yaml
# wrapper templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-pycsw-team-config
data:
pycsw.cfg: {{ tpl (.Files.Get "config/pycsw.cfg") . | quote }}
mappings.py: {{ .Files.Get "config/mappings.py" | quote }}
```

Rendering fails with a clear error when `existingConfigmap` is not set.

### Environment variables available to pycsw.cfg

pycsw expands `${VAR}` placeholders in `pycsw.cfg` at runtime, so the team config
file needs no Helm helpers from this chart. The chart provides:

| Variable | Content |
|---|---|
| `PYCSW_SERVER_URL` | public server URL derived from the nginx route values |
| `PYCSW_MAPPINGS_FILEPATH` | mount path of the team `mappings.py` |
| `PYCSW_DATABASE_CONNECTION` | full postgres connection string (user/password from the DB secret, host/port/name/SSL params from `db`/`global.db` values) |
| `DB_SCHEMA` | value of `env.db.schema` |

A minimal `[repository]` section in a team `pycsw.cfg`:

```ini
[repository]
database=${PYCSW_DATABASE_CONNECTION}
mappings=${PYCSW_MAPPINGS_FILEPATH}
table=${DB_SCHEMA}.records
```

### Values teams are expected to set

| Value | Meaning |
|---|---|
| `existingConfigmap` | name of the team ConfigMap (required) |
| `env.db.schema` | catalog schema, e.g. `RasterCatalogManager` |
| `mclabels.owner`, `mclabels.gisDomain` | team labels |
| `nginx.authorization.domain` | OPA authorization domain |
| `nginx.route.routesMapping` | exposed route paths |
| `replicaCount`, `resources` | scale |

### Multiple instances in one release

When consuming this chart more than once in the same release (aliased
dependencies, e.g. a filtered and an unfiltered catalog), each extra instance
must set:

| Value | Meaning |
|---|---|
| `nameOverride` | distinct pycsw resource names and selector labels |
| `existingConfigmap` | its own team ConfigMap |
| `nginx.nameOverride` | distinct nginx selector labels (nginx >=2.2.1 derives pod selectors from it — identical values make services select each other's pods) |
| `nginx.fullnameOverride` | distinct nginx resource names |
| `nginx.extraVolumes` | point at the instance's own nginx configmap: `{{ .Release.Name }}-<nameOverride>-nginx-configmap` |

## Nginx

Nginx uses the modular extension layout: this chart ships generic
`pycsw-server.conf` / `pycsw-location.conf` snippets mounted into the nginx
subchart's `extensions` directory. Team-specific nginx behavior is driven by
values (`nginx.authorization`, `nginx.route.routesMapping`); additional snippets
can be added through the nginx subchart's own values.

## Config changes and pod restarts

The deployment checksums only the chart-owned ConfigMap (env vars, uwsgi.ini).
Changes to the team ConfigMap (`pycsw.cfg`, `mappings.py`) do **not** restart pods
automatically — run a rollout restart after changing team config:

```bash
kubectl rollout restart deployment <release>-pycsw-deployment
```
6 changes: 6 additions & 0 deletions helm/pycsw/config/pycsw-location.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

# pycsw custom uwsgi settings injected into root location
uwsgi_pass {{ include "pycsw.service.fullname" . }}:{{ .Values.servicePort }};
include uwsgi_params;
uwsgi_hide_header Set-Cookie;
uwsgi_param HTTP_Cookie "";
10 changes: 10 additions & 0 deletions helm/pycsw/config/pycsw-server.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# pycsw server-level settings
keepalive_timeout 500;
proxy_connect_timeout 600;
proxy_send_timeout 600;
send_timeout 600;
client_max_body_size {{ .Values.nginx.clientMaxBodySize }};
client_header_timeout 600;
client_body_timeout 600;
client_header_buffer_size 12288; # 12K
large_client_header_buffers 4 12288; # 12K
38 changes: 38 additions & 0 deletions helm/pycsw/config/pycswWsgi.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[uwsgi]
; based on https://github.com/kartoza/docker-mapproxy/blob/master/build_data/uwsgi.ini
chdir = /home/pycsw/pycsw
wsgi-file = cors.py
pidfile = /tmp/pycsw.pid
socket = :{{ .Values.pycswPort }}
processes = {{ .Values.env.uwsgi.processes }} ; Maximum number of workers allowed
cheaper = 2 ; Minimum number of workers allowed
enable-threads = true
threads = {{ .Values.env.uwsgi.threads }}
master = true
disable-logging = true
vacuum = true
die-on-term = true ; Shutdown when receiving SIGTERM (default is respawn)
need-app = true ; This parameter prevents uWSGI from starting if it is unable to find or load your application module
max-requests = 1000 ; Restart workers after this many requests
reload-on-rss = 2048 ; Restart workers after this much resident memory
buffer-size = 14336 ; 14K, Set the internal buffer size for uwsgi packet
worker-reload-mercy = 60 ; How long to wait before forcefully killing workers
wsgi-disable-file-wrapper = true
harakiri = 60
py-callos-afterfork = true ; allow workers to trap signals
cheaper-algo = busyness
cheaper-initial = 2 ; Workers created at startup
cheaper-overload = 1 ; Length of a cycle in seconds
cheaper-step = 2 ; How many workers to spawn at a time
cheaper-busyness-multiplier = 30 ; How many cycles to wait before killing workers
cheaper-busyness-min = 20 ; Below this threshold, kill workers (if stable for multiplier cycles)
cheaper-busyness-max = 70 ; Above this threshold, spawn new workers
cheaper-busyness-backlog-alert = 2 ; Spawn emergency workers if more than this many requests are waiting in the queue
cheaper-busyness-backlog-step = 2 ; How many emergency workers to create if there are too many requests in the queue
chmod-socket = 664
uid = 1000
gid = 0
http-socket = :8080
stats = :{{ .Values.env.uwsgi.statsServer.stats }}
stats-http = {{ .Values.env.uwsgi.statsServer.enabled }}
stats-min = {{ .Values.env.uwsgi.statsServer.statsMinify }}
183 changes: 183 additions & 0 deletions helm/pycsw/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
{{/*
Expand the name of the chart. nameOverride allows deploying multiple instances
of this chart in the same release (e.g. as aliased dependencies) without
resource name collisions.
*/}}
{{- define "pycsw.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "pycsw.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "pycsw.labels" -}}
helm.sh/chart: {{ include "pycsw.chart" . }}
{{ include "pycsw.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{ include "mclabels.labels" . }}
{{- end }}

{{/*
Returns the tag of the chart.
*/}}
{{- define "pycsw.tag" -}}
{{- default (printf "v%s" .Chart.AppVersion) .Values.image.tag }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "pycsw.selectorLabels" -}}
app.kubernetes.io/name: {{ include "pycsw.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{ include "mclabels.selectorLabels" . }}
{{- end }}

{{/*
Returns the environment from global if exists or from the chart's values, defaults to development
*/}}
{{- define "pycsw.environment" -}}
{{- if .Values.global.environment }}
{{- .Values.global.environment -}}
{{- else -}}
{{- .Values.environment | default "development" -}}
{{- end -}}
{{- end -}}

{{/*
Returns the pycsw server url based on the provided values, default localhost.
Host and path are taken from nginx.route.host/path when set, otherwise from
the first nginx.route.routesMapping entry.
*/}}
{{- define "pycsw.serverURL" -}}
{{- $route := .Values.nginx.route -}}
{{- $first := (first ($route.routesMapping | default list)) | default dict -}}
{{- $host := $route.host | default $first.host -}}
{{- $path := $route.path | default $first.path -}}
{{- if not $host }}
{{- printf "http://localhost:8000" -}}
{{- else -}}
{{- $protocol := ternary "https" "http" $route.tls.enabled -}}
{{- printf "%s://%s" $protocol $host -}}
{{- if $path -}}
{{- printf "%s" $path -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Returns the cloud provider name from global if exists or from the chart's values, defaults to minikube
*/}}
{{- define "pycsw.cloudProviderFlavor" -}}
{{- if .Values.global.cloudProvider.flavor }}
{{- .Values.global.cloudProvider.flavor -}}
{{- else if .Values.cloudProvider -}}
{{- .Values.cloudProvider.flavor | default "minikube" -}}
{{- else -}}
{{ "minikube" }}
{{- end -}}
{{- end -}}

{{/*
Returns the cloud provider docker registry url from global if exists or from the chart's values
*/}}
{{- define "pycsw.cloudProviderDockerRegistryUrl" -}}
{{- if .Values.global.cloudProvider.dockerRegistryUrl }}
{{- printf "%s/" .Values.global.cloudProvider.dockerRegistryUrl -}}
{{- else if .Values.cloudProvider.dockerRegistryUrl -}}
{{- printf "%s/" .Values.cloudProvider.dockerRegistryUrl -}}
{{- else -}}
{{- end -}}
{{- end -}}

{{/*
Returns the cloud provider image pull secret name from global if exists or from the chart's values
*/}}
{{- define "pycsw.cloudProviderImagePullSecretName" -}}
{{- if .Values.global.cloudProvider.imagePullSecretName }}
{{- .Values.global.cloudProvider.imagePullSecretName -}}
{{- else if .Values.cloudProvider.imagePullSecretName -}}
{{- .Values.cloudProvider.imagePullSecretName -}}
{{- end -}}
{{- end -}}

{{/*
Returns the tracing url from global if exists or from the chart's values
*/}}
{{- define "pycsw.tracingUrl" -}}
{{- if .Values.global.tracing.url }}
{{- .Values.global.tracing.url -}}
{{- else if .Values.cloudProvider -}}
{{- .Values.env.tracing.url -}}
{{- end -}}
{{- end -}}

{{/*
Returns the tracing url from global if exists or from the chart's values
*/}}
{{- define "pycsw.metricsUrl" -}}
{{- if .Values.global.metrics.url }}
{{- .Values.global.metrics.url -}}
{{- else -}}
{{- .Values.env.metrics.url -}}
{{- end -}}
{{- end -}}

{{/*
Returns the name of the ConfigMap provided by the consuming chart, holding
the pycsw.cfg and mappings.py keys. The value may itself be a template.
Fails rendering when not provided.
*/}}
{{- define "pycsw.teamConfigmapName" -}}
{{- $name := include "common.tplvalues.render" (dict "value" .Values.existingConfigmap "context" .) -}}
{{- required "existingConfigmap is required: set it to the name of a ConfigMap containing the keys pycsw.cfg and mappings.py (provided by the consuming chart)" $name -}}
{{- end -}}

{{/*
Returns the postgres connection string as an environment variable value.
Uses Kubernetes dependent-variable syntax $(VAR) so it is resolved at container
start from DB_USER/DB_PASSWORD (secret) and DB_HOST/DB_PORT/DB_NAME (configmap).
Consumed by the team-provided pycsw.cfg via database=${PYCSW_DATABASE_CONNECTION}.
*/}}
{{- define "pycsw.connectionStringEnv" -}}
{{- $db := (include "common.db.merged" .) | fromYaml }}
{{- "postgresql://$(DB_USER)" -}}
{{- if .Values.env.db.requirePassword -}}
{{- ":$(DB_PASSWORD)" -}}
{{- end -}}
{{- "@$(DB_HOST):$(DB_PORT)/$(DB_NAME)" -}}
{{- if $db.sslEnabled -}}
{{- "?sslmode=require" -}}
{{- if $db.secrets.caFileKey -}}
{{- "&sslrootcert=/.postgresql/ca.pem" -}}
{{- end -}}
{{- if $db.secrets.certFileKey -}}
{{- "&sslcert=/.postgresql/cert.pem" -}}
{{- end -}}
{{- if $db.secrets.keyFileKey -}}
{{- "&sslkey=/.postgresql/key.pem" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{- define "pycsw.cors.allowedHeaders" -}}
{{- $authentication := (include "common.authentication.merged" .) | fromYaml }}
{{- $headerList := list -}}
{{- if ne .Values.env.cors.allowedHeaders "" -}}
{{- range $k, $v := (split "," .Values.env.cors.allowedHeaders) -}}
{{- $headerList = append $headerList $v -}}
{{- end -}}
{{- $headerList = uniq $headerList -}}
{{- quote (join "," $headerList) -}}
{{- end -}}
{{- end -}}
Loading
Loading