├── charts ├── eoapi │ ├── .gitignore │ ├── templates │ │ ├── core │ │ │ ├── validation.yaml │ │ │ ├── service-account.yaml │ │ │ ├── sink-binding.yaml │ │ │ ├── cloudevents-sink.yaml │ │ │ └── rbac.yaml │ │ ├── services │ │ │ ├── stac │ │ │ │ ├── configmap.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── hpa.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── raster │ │ │ │ ├── configmap.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── hpa.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── vector │ │ │ │ ├── configmap.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── hpa.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── multidim │ │ │ │ ├── configmap.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── hpa.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── browser │ │ │ │ ├── service.yaml │ │ │ │ └── deployment.yaml │ │ │ └── doc-server.yaml │ │ ├── monitoring │ │ │ ├── observability.yaml │ │ │ └── _monitoring.yaml │ │ ├── mock-oidc │ │ │ ├── service.yaml │ │ │ └── deployment.yaml │ │ ├── networking │ │ │ ├── traefik-middleware.yaml │ │ │ └── ingress-browser.yaml │ │ ├── _helpers │ │ │ ├── _resources.tpl │ │ │ ├── core.tpl │ │ │ ├── validation.tpl │ │ │ └── services.tpl │ │ ├── database │ │ │ └── pgstacbootstrap │ │ │ │ ├── queue-processor.yaml │ │ │ │ ├── extent-updater.yaml │ │ │ │ ├── eoap-superuser-initdb.yaml │ │ │ │ └── configmap.yaml │ │ └── NOTES.txt │ ├── initdb-data │ │ ├── samples │ │ │ ├── noaa-emergency-response.json │ │ │ └── my_data.sql │ │ ├── settings │ │ │ ├── pgstac-notification-triggers.sql │ │ │ └── pgstac-settings.sql.tpl │ │ └── queryables │ │ │ └── test-queryables.json │ ├── .helmignore │ ├── values │ │ └── monitoring.yaml │ ├── profiles │ │ ├── local │ │ │ ├── k3s.yaml │ │ │ └── minikube.yaml │ │ ├── README.md │ │ └── core.yaml │ ├── tests │ │ ├── stac_browser_tests.yaml │ │ ├── stac-auth-proxy-ingress_test.yaml │ │ ├── stac_tests.yaml │ │ ├── multidim_tests.yaml │ │ ├── vector_tests.yaml │ │ ├── raster_tests.yaml │ │ ├── postgres_tests.yaml │ │ └── pgstac_notification_tests.yaml │ └── Chart.yaml └── postgrescluster │ ├── templates │ ├── _gcs.tpl │ ├── _azure.tpl │ ├── _s3.tpl │ ├── pgbackrest-secret.yaml │ └── NOTES.txt │ ├── Chart.yaml │ └── README.md ├── docs ├── images │ ├── datasource.png │ ├── gfdashboard.png │ ├── grafanaautoscale.png │ ├── default_architecture.png │ ├── add-grafana-dashboard.png │ ├── eoapi-k8s-raw.svg │ └── eoapi-k8s.svg ├── _includes │ └── repository-links.md ├── index.md ├── release.md ├── manage-data.md ├── docs-config.json ├── helm-install.md ├── quick-start.md └── argocd.md ├── tests ├── requirements.txt └── integration │ ├── test_stac_auth.py │ └── test_vector.py ├── .gitignore ├── .github └── workflows │ ├── pr.yaml │ ├── release.yml │ ├── stac-browser.yml │ └── ci.yml ├── LICENSE ├── .yamllint.yaml ├── mkdocs.yml ├── scripts ├── lib │ └── README.md ├── test │ ├── notification.sh │ └── integration.sh ├── README.md └── ingest.sh ├── README.md ├── .pre-commit-config.yaml ├── eoapi-cli └── renovate.json /charts/eoapi/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | Chart.lock 3 | -------------------------------------------------------------------------------- /docs/images/datasource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/eoapi-k8s/HEAD/docs/images/datasource.png -------------------------------------------------------------------------------- /docs/images/gfdashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/eoapi-k8s/HEAD/docs/images/gfdashboard.png -------------------------------------------------------------------------------- /docs/images/grafanaautoscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/eoapi-k8s/HEAD/docs/images/grafanaautoscale.png -------------------------------------------------------------------------------- /docs/images/default_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/eoapi-k8s/HEAD/docs/images/default_architecture.png -------------------------------------------------------------------------------- /docs/images/add-grafana-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/eoapi-k8s/HEAD/docs/images/add-grafana-dashboard.png -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | # Test dependencies for eoAPI tests 2 | 3 | httpx==0.27.0 4 | requests==2.31.0 5 | 6 | pytest==8.3.2 7 | pytest-timeout==2.3.1 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .pytest_cache 4 | charts/config.yaml 5 | charts/eoapi/charts/*.tgz 6 | config_ingress.yaml 7 | dist 8 | site 9 | __pycache__ 10 | CLAUDE.md 11 | -------------------------------------------------------------------------------- /charts/postgrescluster/templates/_gcs.tpl: -------------------------------------------------------------------------------- 1 | {{/* Allow for GCS secret information to be stored in a Secret */}} 2 | {{- define "postgres.gcs" }} 3 | [global] 4 | {{- if .gcs }} 5 | repo{{ add .index 1 }}-gcs-key=/etc/pgbackrest/conf.d/gcs-key.json 6 | {{- end }} 7 | {{ end }} 8 | -------------------------------------------------------------------------------- /charts/eoapi/templates/core/validation.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | This template validates various configurations. 3 | It doesn't create any resources but ensures configuration consistency. 4 | */}} 5 | {{- include "eoapi.validatePostgresql" . }} 6 | {{- include "eoapi.validateStacAuthProxy" . }} 7 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/stac/configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.stac.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-stac-envvar-configmap 6 | data: 7 | {{- range $envKey, $envValue := .Values.stac.settings.envVars }} 8 | {{ upper $envKey }}: {{ $envValue | quote }} 9 | {{- end }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /charts/eoapi/templates/monitoring/observability.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.observability.grafana.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-dashboards 6 | labels: 7 | eoapi_dashboard: "1" 8 | data: 9 | kubernetes.json: |- 10 | {{ .Files.Get "dashboards/eoAPI-Dashboard.json" | indent 4 }} 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/raster/configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.raster.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-raster-envvar-configmap 6 | data: 7 | {{- range $envKey, $envValue := .Values.raster.settings.envVars }} 8 | {{ upper $envKey }}: {{ $envValue | quote }} 9 | {{- end }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/vector/configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.vector.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-vector-envvar-configmap 6 | data: 7 | {{- range $envKey, $envValue := .Values.vector.settings.envVars }} 8 | {{ upper $envKey }}: {{ $envValue | quote }} 9 | {{- end }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /charts/eoapi/initdb-data/samples/noaa-emergency-response.json: -------------------------------------------------------------------------------- 1 | {"id":"noaa-emergency-response", "title": "NOAA Emergency Response Imagery", "description":"NOAA Emergency Response Imagery hosted on AWS Public Dataset.","stac_version":"1.0.0","license":"public-domain","links":[],"extent":{"spatial":{"bbox":[[-180,-90,180,90]]},"temporal":{"interval":[["2005-01-01T00:00:00Z",null]]}}} 2 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/multidim/configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.multidim.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-multidim-envvar-configmap 6 | data: 7 | {{- range $envKey, $envValue := .Values.multidim.settings.envVars }} 8 | {{ upper $envKey }}: {{ $envValue | quote }} 9 | {{- end }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /charts/postgrescluster/templates/_azure.tpl: -------------------------------------------------------------------------------- 1 | {{/* Allow for Azure secret information to be stored in a Secret */}} 2 | {{- define "postgres.azure" }} 3 | [global] 4 | {{- if .azure }} 5 | {{- if .azure.account }} 6 | repo{{ add .index 1 }}-azure-account={{ .azure.account }} 7 | {{- end }} 8 | {{- if .azure.key }} 9 | repo{{ add .index 1 }}-azure-key={{ .azure.key }} 10 | {{- end }} 11 | {{- end }} 12 | {{ end }} 13 | -------------------------------------------------------------------------------- /charts/eoapi/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | tests/ 25 | 26 | # Documentation files in templates 27 | templates/*/*.md 28 | -------------------------------------------------------------------------------- /docs/_includes/repository-links.md: -------------------------------------------------------------------------------- 1 | 2 | [Main eoapi Repository]: https://github.com/developmentseed/eoapi 3 | [eoapi-k8s Repository]: https://github.com/developmentseed/eoapi-k8s 4 | [Report Issues]: https://github.com/developmentseed/eoapi-k8s/issues 5 | [eoAPI Documentation]: https://eoapi.dev/ 6 | [Helm Charts]: https://github.com/developmentseed/eoapi-k8s/tree/main/charts 7 | [PostgreSQL Operator]: https://access.crunchydata.com/documentation/postgres-operator/ 8 | [Kubernetes Documentation]: https://kubernetes.io/docs/ 9 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/browser/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.browser.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Release.Name }}-browser 6 | labels: 7 | app: {{ .Release.Name }}-browser 8 | {{- if .Values.browser.annotations }} 9 | annotations: 10 | {{- with .Values.browser.annotations }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | selector: 16 | app: {{ .Release.Name }}-browser 17 | ports: 18 | - protocol: TCP 19 | port: 8080 20 | targetPort: 8080 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/stac/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.stac.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Release.Name }}-stac 6 | labels: 7 | app: {{ .Release.Name }}-stac 8 | {{- if .Values.stac.annotations }} 9 | annotations: 10 | {{- with .Values.stac.annotations }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | ports: 16 | - port: {{ .Values.service.port }} 17 | targetPort: {{ .Values.service.port }} 18 | selector: 19 | app: {{ .Release.Name }}-stac 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/raster/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.raster.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Release.Name }}-raster 6 | labels: 7 | app: {{ .Release.Name }}-raster 8 | {{- if .Values.raster.annotations }} 9 | annotations: 10 | {{- with .Values.raster.annotations }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | ports: 16 | - port: {{ .Values.service.port }} 17 | targetPort: {{ .Values.service.port }} 18 | selector: 19 | app: {{ .Release.Name }}-raster 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/vector/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.vector.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Release.Name }}-vector 6 | labels: 7 | app: {{ .Release.Name }}-vector 8 | {{- if .Values.vector.annotations }} 9 | annotations: 10 | {{- with .Values.vector.annotations }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | ports: 16 | - port: {{ .Values.service.port }} 17 | targetPort: {{ .Values.service.port }} 18 | selector: 19 | app: {{ .Release.Name }}-vector 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/multidim/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.multidim.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Release.Name }}-multidim 6 | labels: 7 | app: {{ .Release.Name }}-multidim 8 | {{- if .Values.multidim.annotations }} 9 | annotations: 10 | {{- with .Values.multidim.annotations }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | spec: 15 | ports: 16 | - port: {{ .Values.service.port }} 17 | targetPort: {{ .Values.service.port }} 18 | selector: 19 | app: {{ .Release.Name }}-multidim 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/postgrescluster/templates/_s3.tpl: -------------------------------------------------------------------------------- 1 | {{/* Allow for S3 secret information to be stored in a Secret */}} 2 | {{- define "postgres.s3" }} 3 | [global] 4 | {{- if .s3 }} 5 | {{- if .s3.key }} 6 | repo{{ add .index 1 }}-s3-key={{ .s3.key }} 7 | {{- end }} 8 | {{- if .s3.keySecret }} 9 | repo{{ add .index 1 }}-s3-key-secret={{ .s3.keySecret }} 10 | {{- end }} 11 | {{- if .s3.keyType }} 12 | repo{{ add .index 1 }}-s3-key-type={{ .s3.keyType }} 13 | {{- end }} 14 | {{- if .s3.encryptionPassphrase }} 15 | repo{{ add .index 1 }}-cipher-pass={{ .s3.encryptionPassphrase }} 16 | {{- end }} 17 | {{- end }} 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /charts/eoapi/templates/core/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "eoapi.serviceAccountName" . }} 6 | labels: 7 | app: eoapi-eoapi-{{ .Release.Name }} 8 | {{- range $key, $value := .Values.serviceAccount.labels }} 9 | {{ $key }}: {{ $value | quote }} 10 | {{- end }} 11 | {{- if .Values.serviceAccount.annotations }} 12 | annotations: 13 | {{- range $key, $value := .Values.serviceAccount.annotations }} 14 | {{ $key }}: {{ $value | quote }} 15 | {{- end }} 16 | {{- end }} 17 | automountServiceAccountToken: {{ default true .Values.serviceAccount.automount }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: PR Checks 2 | on: 3 | pull_request: 4 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 5 | 6 | jobs: 7 | pr-title-and-changelog: 8 | name: PR Validation 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v6 13 | 14 | - name: Check Changelog 15 | uses: dangoslen/changelog-enforcer@v3 16 | with: 17 | skipLabels: "skip-changelog, dependencies" 18 | expectedLatestVersion: "" 19 | 20 | - name: Validate PR title 21 | uses: amannn/action-semantic-pull-request@v6 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/browser/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.browser.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ .Release.Name }}-browser 6 | labels: 7 | app: {{ .Release.Name }}-browser 8 | gitsha: {{ .Values.gitSha }} 9 | spec: 10 | replicas: {{.Values.browser.replicaCount}} 11 | selector: 12 | matchLabels: 13 | app: {{ .Release.Name }}-browser 14 | template: 15 | metadata: 16 | labels: 17 | app: {{ .Release.Name }}-browser 18 | spec: 19 | containers: 20 | - name: browser 21 | image: {{ .Values.browser.image.name }}:{{ .Values.browser.image.tag }} 22 | ports: 23 | - containerPort: 8080 24 | env: 25 | - name: SB_catalogUrl 26 | value: "{{ .Values.stac.ingress.path }}" 27 | {{- end }} 28 | -------------------------------------------------------------------------------- /charts/eoapi/templates/mock-oidc/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.mockOidcServer.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "eoapi.fullname" . }}-mock-oidc-server 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "eoapi.labels" . | nindent 4 }} 9 | app.kubernetes.io/component: mock-oidc-server 10 | spec: 11 | type: {{ if .Values.mockOidcServer.service }}{{ .Values.mockOidcServer.service.type | default "ClusterIP" }}{{ else }}ClusterIP{{ end }} 12 | ports: 13 | - port: {{ if .Values.mockOidcServer.service }}{{ .Values.mockOidcServer.service.port | default 8080 }}{{ else }}8080{{ end }} 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | {{- include "eoapi.selectorLabels" . | nindent 4 }} 19 | app.kubernetes.io/component: mock-oidc-server 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/eoapi/templates/networking/traefik-middleware.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.ingress.enabled (eq .Values.ingress.className "traefik") }} 2 | apiVersion: traefik.io/v1alpha1 3 | kind: Middleware 4 | metadata: 5 | name: {{ .Release.Name }}-strip-prefix-middleware 6 | namespace: {{ .Release.Namespace }} 7 | spec: 8 | stripPrefix: 9 | prefixes: 10 | {{- if .Values.raster.enabled }} 11 | - {{ .Values.raster.ingress.path }} 12 | {{- end }} 13 | {{- if .Values.stac.enabled }} 14 | - {{ .Values.stac.ingress.path }} 15 | {{- end }} 16 | {{- if .Values.vector.enabled }} 17 | - {{ .Values.vector.ingress.path }} 18 | {{- end }} 19 | {{- if .Values.multidim.enabled }} 20 | - {{ .Values.multidim.ingress.path }} 21 | {{- end }} 22 | {{- if and .Values.mockOidcServer.enabled .Values.mockOidcServer.ingress.enabled }} 23 | - {{ .Values.mockOidcServer.ingress.path }} 24 | {{- end }} 25 | {{- end }} 26 | -------------------------------------------------------------------------------- /charts/postgrescluster/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: postgrescluster 3 | description: A Helm chart for eoapi database dep for k8s 4 | type: application 5 | # The version below should match the version on the PostgresCluster CRD 6 | # from CrunchyData's Postgres Operator 7 | # https://access.crunchydata.com/documentation/postgres-operator/latest/releases 8 | version: 5.7.4 9 | appVersion: 5.7.4 10 | # Removed invalid 'force' property 11 | 12 | # Artifacthub metadata 13 | annotations: 14 | artifacthub.io/changes: | 15 | - Adds integration with Artifacthub.io 16 | artifacthub.io/links: | 17 | - name: GitHub Repository 18 | url: https://github.com/developmentseed/eoapi-k8s 19 | - name: Documentation 20 | url: https://github.com/developmentseed/eoapi-k8s/tree/main/docs 21 | artifacthub.io/maintainers: | 22 | - name: DevelopmentSeed 23 | email: eoapi@developmentseed.org 24 | artifacthub.io/keywords: | 25 | - postgres 26 | - database 27 | - postgis 28 | - pgstac 29 | - kubernetes 30 | -------------------------------------------------------------------------------- /charts/eoapi/templates/_helpers/_resources.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Common resource definitions to avoid duplication across values files 3 | */}} 4 | 5 | {{/* 6 | Small resource allocation for lightweight components 7 | */}} 8 | {{- define "eoapi.resources.small" -}} 9 | limits: 10 | cpu: 10m 11 | memory: 30Mi 12 | requests: 13 | cpu: 10m 14 | memory: 30Mi 15 | {{- end -}} 16 | 17 | {{/* 18 | Medium resource allocation for standard services 19 | */}} 20 | {{- define "eoapi.resources.medium" -}} 21 | limits: 22 | cpu: 100m 23 | memory: 128Mi 24 | requests: 25 | cpu: 50m 26 | memory: 64Mi 27 | {{- end -}} 28 | 29 | {{/* 30 | Large resource allocation for heavy workloads 31 | */}} 32 | {{- define "eoapi.resources.large" -}} 33 | limits: 34 | cpu: 500m 35 | memory: 512Mi 36 | requests: 37 | cpu: 250m 38 | memory: 256Mi 39 | {{- end -}} 40 | 41 | {{/* 42 | Grafana specific resources based on observed usage patterns 43 | */}} 44 | {{- define "eoapi.resources.grafana" -}} 45 | limits: 46 | cpu: 100m 47 | memory: 200Mi 48 | requests: 49 | cpu: 50m 50 | memory: 100Mi 51 | {{- end -}} 52 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "eoAPI Kubernetes" 3 | description: "Production-ready Kubernetes deployment" 4 | external_links: 5 | - name: "eoapi-k8s Repository" 6 | url: "https://github.com/developmentseed/eoapi-k8s" 7 | --- 8 | 9 | # eoAPI Kubernetes 10 | 11 | Production-ready Kubernetes deployment for eoAPI. 12 | 13 | The source code is maintained in the [eoapi-k8s repository](https://github.com/developmentseed/eoapi-k8s). Contributions are welcome! 14 | 15 | ## Kubernetes Architecture 16 | 17 | This deployment provides: 18 | 19 | - Path-based ingress routing (`/stac`, `/raster`, `/vector`, `/browser`, ..) 20 | - A PostgreSQL cluster (via PostgreSQL Operator) 21 | - TLS termination and certificate management 22 | - Persistent storage with dynamic volume provisioning 23 | - Horizontal pod autoscaling with custom metrics 24 | - Built-in health checks and monitoring at `/stac/_mgmt/ping`, `/raster/healthz`, `/vector/healthz` 25 | 26 | ## Getting Started 27 | 28 | Ready to deploy? Start with our [Quick Start guide](./quick-start.md) for fast installation, or explore the full documentation below for production deployments. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Development Seed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /charts/eoapi/templates/core/sink-binding.yaml: -------------------------------------------------------------------------------- 1 | {{- $hasCloudEventsOutput := false }} 2 | {{- range (index .Values "eoapi-notifier").outputs }} 3 | {{- if eq .type "cloudevents" }} 4 | {{- $hasCloudEventsOutput = true }} 5 | {{- end }} 6 | {{- end }} 7 | {{- if and (index .Values "eoapi-notifier").enabled .Values.knative.enabled .Values.knative.cloudEventsSink.enabled $hasCloudEventsOutput }} 8 | --- 9 | apiVersion: sources.knative.dev/v1 10 | kind: SinkBinding 11 | metadata: 12 | name: eoapi-notifier-sink-binding 13 | namespace: {{ .Release.Namespace }} 14 | labels: 15 | {{- include "eoapi.labels" . | nindent 4 }} 16 | app.kubernetes.io/component: sink-binding 17 | annotations: 18 | helm.sh/hook: "post-install,post-upgrade" 19 | helm.sh/hook-weight: "30" 20 | helm.sh/hook-delete-policy: "before-hook-creation" 21 | spec: 22 | subject: 23 | apiVersion: apps/v1 24 | kind: Deployment 25 | selector: 26 | matchLabels: 27 | app.kubernetes.io/name: eoapi-notifier 28 | sink: 29 | ref: 30 | apiVersion: serving.knative.dev/v1 31 | kind: Service 32 | name: eoapi-cloudevents-sink 33 | namespace: {{ .Release.Namespace }} 34 | {{- end }} 35 | -------------------------------------------------------------------------------- /charts/eoapi/templates/monitoring/_monitoring.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | Common monitoring configurations to avoid duplication across values files 3 | */}} 4 | 5 | {{/* 6 | Standard monitoring configuration with environment-specific settings 7 | Usage: {{ include "eoapi.monitoring.config" (dict "context" . "environment" "production" "persistence" true) }} 8 | Environments: basic, production, testing 9 | */}} 10 | {{- define "eoapi.monitoring.config" -}} 11 | {{- $ctx := .context -}} 12 | {{- $env := .environment | default "basic" -}} 13 | {{- $persistence := .persistence | default false -}} 14 | metricsServer: 15 | enabled: true 16 | apiService: 17 | create: true 18 | prometheus: 19 | enabled: true 20 | alertmanager: 21 | enabled: {{ if eq $env "production" }}true{{ else }}false{{ end }} 22 | prometheus-pushgateway: 23 | enabled: false 24 | kube-state-metrics: 25 | enabled: true 26 | prometheus-node-exporter: 27 | enabled: true 28 | resources: {{- include "eoapi.resources.small" $ctx | nindent 6 }} 29 | server: 30 | service: 31 | type: ClusterIP 32 | {{- if $persistence }} 33 | persistentVolume: 34 | enabled: true 35 | size: 10Gi 36 | {{- else }} 37 | persistentVolume: 38 | enabled: false 39 | {{- end }} 40 | {{- end -}} 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release helm chart 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | env: 9 | HELM_VERSION: v3.15.2 10 | 11 | jobs: 12 | release: 13 | if: ${{ !startsWith(github.ref, 'refs/tags/eoapi-') }} # prevent the helm chart releaser from running this release workflow 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: configure git 21 | run: | 22 | git config user.name "$GITHUB_ACTOR" 23 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 24 | 25 | - uses: azure/setup-helm@v4 26 | with: 27 | version: ${{ env.HELM_VERSION }} 28 | 29 | - name: add helm repos 30 | run: | 31 | helm repo add eoapi https://devseed.com/eoapi-k8s/ 32 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 33 | helm repo add grafana https://grafana.github.io/helm-charts 34 | helm repo add bitnami https://charts.bitnami.com/bitnami 35 | helm repo list 36 | 37 | - name: run chart-releaser 38 | uses: helm/chart-releaser-action@v1.7.0 39 | with: 40 | charts_dir: charts 41 | env: 42 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 43 | CR_SKIP_EXISTING: true 44 | CR_INDEX_PATH: "." 45 | -------------------------------------------------------------------------------- /charts/eoapi/initdb-data/settings/pgstac-notification-triggers.sql: -------------------------------------------------------------------------------- 1 | -- Create the notification function 2 | CREATE OR REPLACE FUNCTION notify_items_change_func() 3 | RETURNS TRIGGER AS $$ 4 | DECLARE 5 | 6 | BEGIN 7 | PERFORM pg_notify('pgstac_items_change'::text, json_build_object( 8 | 'operation', TG_OP, 9 | 'items', jsonb_agg( 10 | jsonb_build_object( 11 | 'collection', data.collection, 12 | 'id', data.id 13 | ) 14 | ) 15 | )::text 16 | ) 17 | FROM data 18 | ; 19 | RETURN NULL; 20 | END; 21 | $$ LANGUAGE plpgsql; 22 | 23 | -- Create triggers for INSERT operations 24 | CREATE OR REPLACE TRIGGER notify_items_change_insert 25 | AFTER INSERT ON pgstac.items 26 | REFERENCING NEW TABLE AS data 27 | FOR EACH STATEMENT EXECUTE FUNCTION notify_items_change_func() 28 | ; 29 | 30 | -- Create triggers for UPDATE operations 31 | CREATE OR REPLACE TRIGGER notify_items_change_update 32 | AFTER UPDATE ON pgstac.items 33 | REFERENCING NEW TABLE AS data 34 | FOR EACH STATEMENT EXECUTE FUNCTION notify_items_change_func() 35 | ; 36 | 37 | -- Create triggers for DELETE operations 38 | CREATE OR REPLACE TRIGGER notify_items_change_delete 39 | AFTER DELETE ON pgstac.items 40 | REFERENCING OLD TABLE AS data 41 | FOR EACH STATEMENT EXECUTE FUNCTION notify_items_change_func() 42 | ; 43 | -------------------------------------------------------------------------------- /charts/eoapi/values/monitoring.yaml: -------------------------------------------------------------------------------- 1 | ###################### 2 | # MONITORING BASE CONFIG 3 | ###################### 4 | # Base monitoring configuration - import in values files with: 5 | # monitoring: !include values/monitoring.yaml 6 | 7 | monitoring: 8 | enabled: true 9 | 10 | # Metrics server for HPA 11 | metricsServer: 12 | enabled: true 13 | apiService: 14 | create: true 15 | resources: &small_resources 16 | limits: 17 | cpu: 10m 18 | memory: 30Mi 19 | requests: 20 | cpu: 10m 21 | memory: 30Mi 22 | 23 | # Prometheus stack 24 | prometheus: 25 | enabled: true 26 | alertmanager: 27 | enabled: false 28 | prometheus-pushgateway: 29 | enabled: false 30 | 31 | kube-state-metrics: 32 | enabled: true 33 | resources: *small_resources 34 | 35 | prometheus-node-exporter: 36 | enabled: true 37 | resources: *small_resources 38 | 39 | server: 40 | service: 41 | type: ClusterIP 42 | persistentVolume: 43 | enabled: false 44 | size: 8Gi 45 | resources: 46 | limits: 47 | cpu: 500m 48 | memory: 512Mi 49 | requests: 50 | cpu: 200m 51 | memory: 256Mi 52 | 53 | # Autoscaling defaults 54 | autoscaling: 55 | enabled: false 56 | minReplicas: 1 57 | maxReplicas: 10 58 | targetCPUUtilizationPercentage: 80 59 | targetMemoryUtilizationPercentage: 80 60 | -------------------------------------------------------------------------------- /charts/eoapi/profiles/local/k3s.yaml: -------------------------------------------------------------------------------- 1 | # eoAPI k3s Local Profile 2 | # k3s-specific overrides for local development 3 | # Must be used together with experimental.yaml profile 4 | # 5 | # Usage: 6 | # helm install eoapi ./charts/eoapi -f profiles/experimental.yaml -f profiles/local/k3s.yaml 7 | # helm upgrade eoapi ./charts/eoapi -f profiles/experimental.yaml -f profiles/local/k3s.yaml 8 | # 9 | # Note: This profile inherits all settings from experimental.yaml and only overrides k3s-specific values 10 | 11 | ###################### 12 | # K3S SPECIFIC INGRESS 13 | ###################### 14 | # k3s uses Traefik as the default ingress controller 15 | ingress: 16 | className: "traefik" 17 | annotations: 18 | traefik.ingress.kubernetes.io/router.entrypoints: web 19 | 20 | ###################### 21 | # POSTGRESQL RESOURCES 22 | ###################### 23 | # Reduce PostgreSQL resources for local k3s development 24 | postgrescluster: 25 | instances: 26 | - name: "eoapi" 27 | replicas: 1 28 | dataVolumeClaimSpec: 29 | accessModes: 30 | - "ReadWriteOnce" 31 | resources: 32 | requests: 33 | storage: "1Gi" 34 | resources: 35 | requests: 36 | cpu: "100m" 37 | memory: "512Mi" 38 | limits: 39 | cpu: "500m" 40 | memory: "1Gi" 41 | 42 | ###################### 43 | # MONITORING 44 | ###################### 45 | # Disable metrics-server as k3s provides it built-in 46 | monitoring: 47 | metricsServer: 48 | enabled: false 49 | -------------------------------------------------------------------------------- /charts/postgrescluster/templates/pgbackrest-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.enabled }} 2 | {{- if or .Values.multiBackupRepos .Values.s3 .Values.gcs .Values.azure }} 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: {{ default .Release.Name .Values.name }}-pgbackrest-secret 7 | type: Opaque 8 | data: 9 | {{- if .Values.multiBackupRepos }} 10 | {{- range $index, $repo := .Values.multiBackupRepos }} 11 | {{- if $repo.s3 }} 12 | {{- $args := dict "s3" $repo.s3 "index" $index }} 13 | s3.conf: |- 14 | {{ include "postgres.s3" $args | b64enc }} 15 | {{- else if $repo.gcs }} 16 | {{- $args := dict "gcs" $repo.gcs "index" $index }} 17 | gcs.conf: |- 18 | {{ include "postgres.gcs" $args | b64enc }} 19 | gcs-key.json: |- 20 | {{ $repo.gcs.key | b64enc }} 21 | {{- else if $repo.azure }} 22 | {{- $args := dict "azure" $repo.azure "index" $index }} 23 | azure.conf: |- 24 | {{ include "postgres.azure" $args | b64enc }} 25 | {{- end }} 26 | {{- end }} 27 | {{- else if .Values.s3 }} 28 | {{- $args := dict "s3" .Values.s3 "index" 0 }} 29 | s3.conf: |- 30 | {{ include "postgres.s3" $args | b64enc }} 31 | {{- else if .Values.gcs }} 32 | {{- $args := dict "gcs" .Values.gcs "index" 0 }} 33 | gcs.conf: |- 34 | {{ include "postgres.gcs" $args | b64enc }} 35 | gcs-key.json: |- 36 | {{ .Values.gcs.key | b64enc }} 37 | {{- else if .Values.azure }} 38 | {{- $args := dict "azure" .Values.azure "index" 0 }} 39 | azure.conf: |- 40 | {{ include "postgres.azure" $args | b64enc }} 41 | {{- end }} 42 | {{- end }} 43 | {{- end }} 44 | -------------------------------------------------------------------------------- /charts/eoapi/profiles/local/minikube.yaml: -------------------------------------------------------------------------------- 1 | # eoAPI Minikube Local Profile 2 | # Minikube-specific overrides for local development 3 | # Must be used together with experimental.yaml profile 4 | # 5 | # Usage: 6 | # helm install eoapi ./charts/eoapi -f profiles/experimental.yaml -f profiles/local/minikube.yaml 7 | # helm upgrade eoapi ./charts/eoapi -f profiles/experimental.yaml -f profiles/local/minikube.yaml 8 | # 9 | # Note: This profile inherits all settings from experimental.yaml and only overrides minikube-specific values 10 | 11 | ###################### 12 | # MINIKUBE SPECIFIC INGRESS 13 | ###################### 14 | # Minikube uses nginx as the default ingress controller 15 | ingress: 16 | className: "nginx" 17 | annotations: 18 | nginx.ingress.kubernetes.io/rewrite-target: /$2 19 | nginx.ingress.kubernetes.io/use-regex: "true" 20 | 21 | ###################### 22 | # POSTGRESQL RESOURCES 23 | ###################### 24 | # Reduce PostgreSQL resources for local minikube development 25 | postgrescluster: 26 | instances: 27 | - name: "postgres" 28 | replicas: 1 29 | dataVolumeClaimSpec: 30 | accessModes: 31 | - "ReadWriteOnce" 32 | resources: 33 | requests: 34 | storage: "1Gi" 35 | resources: 36 | requests: 37 | cpu: "100m" 38 | memory: "512Mi" 39 | limits: 40 | cpu: "500m" 41 | memory: "1Gi" 42 | 43 | ###################### 44 | # MONITORING 45 | ###################### 46 | # Keep metrics-server enabled as minikube may not provide it by default 47 | monitoring: 48 | metricsServer: 49 | enabled: true 50 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | # yamllint configuration for Kubernetes/Helm YAML files 2 | # See https://yamllint.readthedocs.io/en/stable/configuration.html 3 | 4 | extends: default 5 | 6 | rules: 7 | # Allow longer lines for Kubernetes manifests (image names, URLs, etc.) 8 | line-length: 9 | max: 180 10 | level: warning 11 | 12 | # Allow multiple documents in single files (common in K8s) 13 | document-start: disable 14 | 15 | # Relax indentation rules - K8s manifests can have complex nesting 16 | indentation: 17 | spaces: consistent 18 | indent-sequences: consistent 19 | check-multi-line-strings: false 20 | 21 | # Allow empty values (common in Helm templates) 22 | empty-values: 23 | forbid-in-block-mappings: false 24 | forbid-in-flow-mappings: false 25 | 26 | # Allow truthy values like "yes", "no", "on", "off" (common in K8s) 27 | truthy: 28 | allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off'] 29 | check-keys: false 30 | 31 | # Be more lenient with comments 32 | comments: 33 | min-spaces-from-content: 1 34 | require-starting-space: true 35 | 36 | # Allow quoted strings (often necessary for Helm templating) 37 | quoted-strings: 38 | quote-type: any 39 | required: false 40 | 41 | # Allow brackets for flow sequences/mappings 42 | brackets: 43 | min-spaces-inside: 0 44 | max-spaces-inside: 1 45 | 46 | # Allow braces for flow mappings 47 | braces: 48 | min-spaces-inside: 0 49 | max-spaces-inside: 1 50 | 51 | # Ignore certain files/patterns 52 | ignore: | 53 | charts/*/charts/ 54 | charts/*/tmpcharts/ 55 | charts/*/templates/ 56 | tests/integration/ 57 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: eoAPI Kubernetes 2 | site_description: Production-ready Kubernetes deployment for eoAPI - Earth Observation data APIs 3 | site_url: https://developmentseed.github.io/eoapi-k8s 4 | repo_url: https://github.com/developmentseed/eoapi-k8s 5 | repo_name: developmentseed/eoapi-k8s 6 | edit_uri: edit/main/docs/ 7 | 8 | theme: 9 | name: material 10 | palette: 11 | - scheme: default 12 | primary: blue 13 | accent: blue 14 | features: 15 | - navigation.tabs 16 | - navigation.sections 17 | - navigation.expand 18 | - navigation.top 19 | - search.highlight 20 | - content.code.copy 21 | 22 | docs_dir: docs 23 | site_dir: site 24 | 25 | nav: 26 | - Home: index.md 27 | - Quick Start: quick-start.md 28 | - Installation: 29 | - Helm Installation: helm-install.md 30 | - Configuration Options: configuration.md 31 | - Unified Ingress: unified-ingress.md 32 | - Cloud Providers: 33 | - AWS EKS: aws-eks.md 34 | - GCP GKE: gcp-gke.md 35 | - Azure AKS: azure.md 36 | - Operations: 37 | - Data Management: manage-data.md 38 | - Autoscaling: autoscaling.md 39 | - Observability: observability.md 40 | - Authentication: 41 | - STAC Auth Proxy: stac-auth-proxy.md 42 | - Contributing: 43 | - Release Workflow: release.md 44 | 45 | markdown_extensions: 46 | - admonition 47 | - pymdownx.details 48 | - pymdownx.superfences 49 | - pymdownx.highlight: 50 | anchor_linenums: true 51 | - pymdownx.inlinehilite 52 | - pymdownx.snippets 53 | - attr_list 54 | - md_in_html 55 | - toc: 56 | permalink: true 57 | 58 | plugins: 59 | - search 60 | -------------------------------------------------------------------------------- /charts/eoapi/templates/database/pgstacbootstrap/queue-processor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.pgstacBootstrap.enabled }} 2 | {{- if .Values.pgstacBootstrap.settings.pgstacSettings }} 3 | {{- if eq (.Values.pgstacBootstrap.settings.pgstacSettings.use_queue | default "true") "true" }} 4 | --- 5 | apiVersion: batch/v1 6 | kind: CronJob 7 | metadata: 8 | name: {{ .Release.Name }}-pgstac-queue-processor 9 | labels: 10 | {{- include "eoapi.labels" . | nindent 4 }} 11 | app.kubernetes.io/component: pgstac-queue 12 | spec: 13 | schedule: {{ .Values.pgstacBootstrap.settings.queueProcessor.schedule | quote }} 14 | concurrencyPolicy: Forbid 15 | successfulJobsHistoryLimit: 1 16 | failedJobsHistoryLimit: 1 17 | jobTemplate: 18 | spec: 19 | template: 20 | metadata: 21 | labels: 22 | {{- include "eoapi.labels" . | nindent 12 }} 23 | app.kubernetes.io/component: pgstac-queue 24 | spec: 25 | restartPolicy: OnFailure 26 | containers: 27 | - name: queue-processor 28 | image: {{ .Values.pgstacBootstrap.image.name }}:{{ .Values.pgstacBootstrap.image.tag }} 29 | imagePullPolicy: IfNotPresent 30 | command: 31 | - "/bin/sh" 32 | - "-c" 33 | - | 34 | psql -c "CALL run_queued_queries();" 35 | env: 36 | {{- include "eoapi.postgresqlEnv" . | nindent 14 }} 37 | resources: 38 | limits: 39 | cpu: "256m" 40 | memory: "512Mi" 41 | requests: 42 | cpu: "128m" 43 | memory: "256Mi" 44 | {{- end }} 45 | {{- end }} 46 | {{- end }} 47 | -------------------------------------------------------------------------------- /charts/eoapi/templates/database/pgstacbootstrap/extent-updater.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.pgstacBootstrap.enabled }} 2 | {{- if .Values.pgstacBootstrap.settings.pgstacSettings }} 3 | {{- if eq (default "false" .Values.pgstacBootstrap.settings.pgstacSettings.update_collection_extent) "false" }} 4 | --- 5 | apiVersion: batch/v1 6 | kind: CronJob 7 | metadata: 8 | name: {{ .Release.Name }}-pgstac-extent-updater 9 | labels: 10 | {{- include "eoapi.labels" . | nindent 4 }} 11 | app.kubernetes.io/component: pgstac-extents 12 | spec: 13 | schedule: {{ .Values.pgstacBootstrap.settings.extentUpdater.schedule | quote }} 14 | concurrencyPolicy: Forbid 15 | successfulJobsHistoryLimit: 1 16 | failedJobsHistoryLimit: 1 17 | jobTemplate: 18 | spec: 19 | template: 20 | metadata: 21 | labels: 22 | {{- include "eoapi.labels" . | nindent 12 }} 23 | app.kubernetes.io/component: pgstac-extents 24 | spec: 25 | restartPolicy: OnFailure 26 | containers: 27 | - name: extent-updater 28 | image: {{ .Values.pgstacBootstrap.image.name }}:{{ .Values.pgstacBootstrap.image.tag }} 29 | imagePullPolicy: IfNotPresent 30 | command: 31 | - "/bin/sh" 32 | - "-c" 33 | - | 34 | psql -c "SELECT update_collection_extents();" 35 | env: 36 | {{- include "eoapi.postgresqlEnv" . | nindent 14 }} 37 | resources: 38 | limits: 39 | cpu: "512m" 40 | memory: "1024Mi" 41 | requests: 42 | cpu: "256m" 43 | memory: "512Mi" 44 | {{- end }} 45 | {{- end }} 46 | {{- end }} 47 | -------------------------------------------------------------------------------- /charts/eoapi/tests/stac_browser_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: stac browser service tests 2 | templates: 3 | - templates/services/browser/deployment.yaml 4 | - templates/services/browser/service.yaml 5 | tests: 6 | - it: "stac browser deployment" 7 | set: 8 | raster.enabled: false 9 | stac.enabled: false 10 | vector.enabled: false 11 | multidim.enabled: false 12 | browser.enabled: true 13 | gitSha: "ABC123" 14 | template: templates/services/browser/deployment.yaml 15 | asserts: 16 | - isKind: 17 | of: Deployment 18 | - matchRegex: 19 | path: metadata.name 20 | pattern: ^RELEASE-NAME-browser$ 21 | - matchRegex: 22 | path: metadata.labels.app 23 | pattern: ^RELEASE-NAME-browser$ 24 | - equal: 25 | path: metadata.labels.gitsha 26 | value: "ABC123" 27 | - it: "stac browser service" 28 | set: 29 | raster.enabled: false 30 | stac.enabled: false 31 | vector.enabled: false 32 | multidim.enabled: false 33 | browser.enabled: true 34 | browser.annotations: 35 | annotation1: hello 36 | annotation2: world 37 | gitSha: "ABC123" 38 | template: templates/services/browser/service.yaml 39 | asserts: 40 | - isKind: 41 | of: Service 42 | - matchRegex: 43 | path: metadata.name 44 | pattern: ^RELEASE-NAME-browser$ 45 | - matchRegex: 46 | path: metadata.labels.app 47 | pattern: ^RELEASE-NAME-browser$ 48 | - equal: 49 | path: metadata.annotations.annotation1 50 | value: hello 51 | - equal: 52 | path: metadata.annotations.annotation2 53 | value: world 54 | -------------------------------------------------------------------------------- /charts/eoapi/initdb-data/settings/pgstac-settings.sql.tpl: -------------------------------------------------------------------------------- 1 | -- Apply pgstac settings 2 | -- These settings are configured via Helm values at pgstacBootstrap.settings.pgstacSettings 3 | 4 | -- Queue settings 5 | DELETE FROM pgstac.pgstac_settings WHERE name = 'queue_timeout'; 6 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('queue_timeout', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.queue_timeout }}'); 7 | 8 | DELETE FROM pgstac.pgstac_settings WHERE name = 'use_queue'; 9 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('use_queue', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.use_queue }}'); 10 | 11 | -- Collection extent management 12 | DELETE FROM pgstac.pgstac_settings WHERE name = 'update_collection_extent'; 13 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('update_collection_extent', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.update_collection_extent }}'); 14 | 15 | -- Context settings 16 | DELETE FROM pgstac.pgstac_settings WHERE name = 'context'; 17 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('context', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.context }}'); 18 | 19 | DELETE FROM pgstac.pgstac_settings WHERE name = 'context_estimated_count'; 20 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('context_estimated_count', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.context_estimated_count }}'); 21 | 22 | DELETE FROM pgstac.pgstac_settings WHERE name = 'context_estimated_cost'; 23 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('context_estimated_cost', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.context_estimated_cost }}'); 24 | 25 | DELETE FROM pgstac.pgstac_settings WHERE name = 'context_stats_ttl'; 26 | INSERT INTO pgstac.pgstac_settings (name, value) VALUES ('context_stats_ttl', '{{ .Values.pgstacBootstrap.settings.pgstacSettings.context_stats_ttl }}'); 27 | -------------------------------------------------------------------------------- /charts/eoapi/templates/core/cloudevents-sink.yaml: -------------------------------------------------------------------------------- 1 | {{- $hasCloudEventsOutput := false }} 2 | {{- range (index .Values "eoapi-notifier").outputs }} 3 | {{- if eq .type "cloudevents" }} 4 | {{- $hasCloudEventsOutput = true }} 5 | {{- end }} 6 | {{- end }} 7 | {{- if and (index .Values "eoapi-notifier").enabled .Values.knative.enabled .Values.knative.cloudEventsSink.enabled $hasCloudEventsOutput }} 8 | --- 9 | apiVersion: serving.knative.dev/v1 10 | kind: Service 11 | metadata: 12 | name: eoapi-cloudevents-sink 13 | namespace: {{ .Release.Namespace }} 14 | labels: 15 | {{- include "eoapi.labels" . | nindent 4 }} 16 | app.kubernetes.io/component: cloudevents-sink 17 | annotations: 18 | helm.sh/hook: "post-install,post-upgrade" 19 | helm.sh/hook-weight: "10" 20 | helm.sh/hook-delete-policy: "before-hook-creation" 21 | spec: 22 | template: 23 | metadata: 24 | annotations: 25 | autoscaling.knative.dev/minScale: "1" 26 | autoscaling.knative.dev/maxScale: "1" 27 | labels: 28 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 29 | app.kubernetes.io/component: cloudevents-sink 30 | spec: 31 | containers: 32 | - name: cloudevents-sink 33 | image: gcr.io/knative-samples/helloworld-go:latest 34 | ports: 35 | - containerPort: 8080 36 | env: 37 | - name: TARGET 38 | value: "eoAPI CloudEvents Sink" 39 | readinessProbe: 40 | httpGet: 41 | path: / 42 | port: 8080 43 | initialDelaySeconds: 5 44 | periodSeconds: 10 45 | livenessProbe: 46 | httpGet: 47 | path: / 48 | port: 8080 49 | initialDelaySeconds: 15 50 | periodSeconds: 20 51 | resources: 52 | {{- toYaml .Values.knative.cloudEventsSink.resources | nindent 10 }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Release Workflow" 3 | description: "Chart versioning, GitHub releases, and Helm repository publishing process" 4 | external_links: 5 | - name: "eoapi-k8s Repository" 6 | url: "https://github.com/developmentseed/eoapi-k8s" 7 | - name: "Semantic Versioning" 8 | url: "https://semver.org/" 9 | - name: "Helm Chart Best Practices" 10 | url: "https://helm.sh/docs/chart_best_practices/" 11 | --- 12 | 13 | # Release Workflow 14 | 15 | 1. PRs that include changes in the `charts/ || || ` charts are manually required to consider 16 | whether their changes are major, minor or patch (in terms of semantic versioning) and bump the appropriate 17 | chart `version: ` (which follows semver) and `appVersion: ` (which does not follow semver) for each affected chart 18 | 19 | 3. The releaser then merges the above PR 20 | 21 | 4. Then the releaser should go to the Github release UI/UX and kick off a new release by doing the following: 22 | 23 | 1. click "Draft New Release" 24 | 25 | 2. create a new tag increment based on the last one that matches the pattern `v..`. This does not have to match any of the chart versions you changed in the above PR. This repository is one-to-many with charts. So in terms of GH release we are saying, "we've release one of the three charts above" and the commit message will reflect that 26 | 27 | 3. click the "Generate release notes" 28 | 29 | 4. review the release notes and clean up and makes sure talk about which chart you released 30 | 31 | 5. click the "Publish release" 32 | 33 | 34 | 5. This last step then kicks off another GH Actions workflow called "release.yaml" which publishes any helm charts 35 | that had version bumps since the last time 36 | 37 | 6. Verify the release is all good by running `helm repo update && helm search repo eoapi --versions` 38 | -------------------------------------------------------------------------------- /charts/eoapi/initdb-data/queryables/test-queryables.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://localhost/stac/queryables", 4 | "title": "STAC Queryables.", 5 | "type": "object", 6 | "properties": { 7 | "id": { 8 | "$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/properties/id", 9 | "title": "Item ID", 10 | "description": "Item identifier" 11 | }, 12 | "datetime": { 13 | "type": "string", 14 | "title": "Acquired", 15 | "format": "date-time", 16 | "pattern": "(\\+00:00|Z)$", 17 | "description": "Datetime" 18 | }, 19 | "geometry": { 20 | "$ref": "https://geojson.org/schema/Feature.json", 21 | "title": "Item Geometry", 22 | "description": "Item Geometry" 23 | }, 24 | "platform": { 25 | "description": "Platform or satellite name", 26 | "type": "string", 27 | "title": "Platform" 28 | }, 29 | "instruments": { 30 | "description": "Instrument(s) used", 31 | "type": "array", 32 | "title": "Instruments", 33 | "items": { 34 | "type": "string" 35 | } 36 | }, 37 | "eo:cloud_cover": { 38 | "description": "Estimate of cloud cover as a percentage (0-100) of the entire scene", 39 | "type": "number", 40 | "title": "Cloud Cover", 41 | "minimum": 0, 42 | "maximum": 100 43 | }, 44 | "view:sun_azimuth": { 45 | "description": "Sun azimuth angle in degrees", 46 | "type": "number", 47 | "title": "Sun Azimuth", 48 | "minimum": 0, 49 | "maximum": 360 50 | }, 51 | "view:sun_elevation": { 52 | "description": "Sun elevation angle in degrees", 53 | "type": "number", 54 | "title": "Sun Elevation", 55 | "minimum": -90, 56 | "maximum": 90 57 | } 58 | }, 59 | "additionalProperties": true 60 | } 61 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/stac/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.stac.enabled .Values.stac.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ .Release.Name }}-stac-hpa 6 | labels: 7 | app: {{ .Release.Name }}-stac 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ .Release.Name }}-stac 13 | minReplicas: {{ .Values.stac.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.stac.autoscaling.maxReplicas }} 15 | behavior: 16 | {{- with .Values.stac.autoscaling.behavior }} 17 | scaleDown: 18 | stabilizationWindowSeconds: {{ .scaleDown.stabilizationWindowSeconds }} 19 | scaleUp: 20 | stabilizationWindowSeconds: {{ .scaleUp.stabilizationWindowSeconds }} 21 | {{- end }} 22 | metrics: 23 | {{- if eq .Values.stac.autoscaling.type "cpu" }} 24 | - type: Resource 25 | resource: 26 | name: cpu 27 | target: 28 | type: Utilization 29 | averageUtilization: {{ .Values.stac.autoscaling.targets.cpu }} 30 | {{- else if eq .Values.stac.autoscaling.type "requestRate" }} 31 | - type: Pods 32 | pods: 33 | metric: 34 | name: nginx_ingress_controller_requests 35 | target: 36 | type: AverageValue 37 | averageValue: {{ .Values.stac.autoscaling.targets.requestRate }} 38 | {{- else if eq .Values.stac.autoscaling.type "both" }} 39 | - type: Resource 40 | resource: 41 | name: cpu 42 | target: 43 | type: Utilization 44 | averageUtilization: {{ .Values.stac.autoscaling.targets.cpu }} 45 | - type: Pods 46 | pods: 47 | metric: 48 | name: nginx_ingress_controller_requests 49 | target: 50 | type: AverageValue 51 | averageValue: {{ .Values.stac.autoscaling.targets.requestRate }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /scripts/lib/README.md: -------------------------------------------------------------------------------- 1 | # eoAPI Scripts - Shared Utilities 2 | 3 | Shared utility functions for eoAPI deployment, testing, and ingestion scripts. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh" 9 | ``` 10 | 11 | ## Functions 12 | 13 | ### Argument Parsing 14 | 15 | `parse_standard_options "$@"` - Parses standard options and sets: 16 | - `DEBUG_MODE` - Debug output enabled (-d/--debug) 17 | - `NAMESPACE` - Kubernetes namespace (-n/--namespace) 18 | - `REMAINING_ARGS` - Array of non-option arguments 19 | 20 | ### Logging 21 | 22 | - `log_info` - Information messages (blue) 23 | - `log_success` - Success messages (green) 24 | - `log_warn` - Warning messages (yellow) 25 | - `log_error` - Error messages (red, stderr) 26 | - `log_debug` - Debug messages (shown when DEBUG_MODE=true or in CI) 27 | 28 | ### Validation 29 | 30 | - `check_requirements tool1 tool2...` - Verify required tools are installed 31 | - `validate_cluster` - Check kubectl connectivity 32 | - `validate_namespace "namespace"` - Verify namespace exists 33 | - `validate_eoapi_deployment "namespace" "release"` - Validate deployment health 34 | 35 | ### Detection 36 | 37 | - `is_ci` - Returns true if running in CI environment 38 | - `detect_release_name ["namespace"]` - Auto-detect eoAPI release name 39 | - `detect_namespace` - Auto-detect eoAPI namespace from deployed resources 40 | 41 | ### Pre-flight Checks 42 | 43 | - `preflight_deploy` - Validate deployment prerequisites 44 | - `preflight_ingest "namespace" "collections" "items"` - Validate ingestion inputs 45 | - `preflight_test "helm|integration"` - Validate test prerequisites 46 | 47 | ### Utilities 48 | 49 | - `wait_for_pods "namespace" "selector" ["timeout"]` - Wait for pod readiness 50 | - `command_exists "cmd"` - Check if command is available 51 | 52 | ## Error Handling 53 | 54 | Scripts use `set -euo pipefail` and trap EXIT for cleanup. CI environments automatically enable debug mode. 55 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/raster/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.raster.enabled .Values.raster.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ .Release.Name }}-raster-hpa 6 | labels: 7 | app: {{ .Release.Name }}-raster 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ .Release.Name }}-raster 13 | minReplicas: {{ .Values.raster.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.raster.autoscaling.maxReplicas }} 15 | behavior: 16 | {{- with .Values.raster.autoscaling.behavior }} 17 | scaleDown: 18 | stabilizationWindowSeconds: {{ .scaleDown.stabilizationWindowSeconds }} 19 | scaleUp: 20 | stabilizationWindowSeconds: {{ .scaleUp.stabilizationWindowSeconds }} 21 | {{- end }} 22 | metrics: 23 | {{- if eq .Values.raster.autoscaling.type "cpu" }} 24 | - type: Resource 25 | resource: 26 | name: cpu 27 | target: 28 | type: Utilization 29 | averageUtilization: {{ .Values.raster.autoscaling.targets.cpu }} 30 | {{- else if eq .Values.raster.autoscaling.type "requestRate" }} 31 | - type: Pods 32 | pods: 33 | metric: 34 | name: nginx_ingress_controller_requests 35 | target: 36 | type: AverageValue 37 | averageValue: {{ .Values.raster.autoscaling.targets.requestRate }} 38 | {{- else if eq .Values.raster.autoscaling.type "both" }} 39 | - type: Resource 40 | resource: 41 | name: cpu 42 | target: 43 | type: Utilization 44 | averageUtilization: {{ .Values.raster.autoscaling.targets.cpu }} 45 | - type: Pods 46 | pods: 47 | metric: 48 | name: nginx_ingress_controller_requests 49 | target: 50 | type: AverageValue 51 | averageValue: {{ .Values.raster.autoscaling.targets.requestRate }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/vector/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.vector.enabled .Values.vector.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ .Release.Name }}-vector-hpa 6 | labels: 7 | app: {{ .Release.Name }}-vector 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ .Release.Name }}-vector 13 | minReplicas: {{ .Values.vector.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.vector.autoscaling.maxReplicas }} 15 | behavior: 16 | {{- with .Values.vector.autoscaling.behavior }} 17 | scaleDown: 18 | stabilizationWindowSeconds: {{ .scaleDown.stabilizationWindowSeconds }} 19 | scaleUp: 20 | stabilizationWindowSeconds: {{ .scaleUp.stabilizationWindowSeconds }} 21 | {{- end }} 22 | metrics: 23 | {{- if eq .Values.vector.autoscaling.type "cpu" }} 24 | - type: Resource 25 | resource: 26 | name: cpu 27 | target: 28 | type: Utilization 29 | averageUtilization: {{ .Values.vector.autoscaling.targets.cpu }} 30 | {{- else if eq .Values.vector.autoscaling.type "requestRate" }} 31 | - type: Pods 32 | pods: 33 | metric: 34 | name: nginx_ingress_controller_requests 35 | target: 36 | type: AverageValue 37 | averageValue: {{ .Values.vector.autoscaling.targets.requestRate }} 38 | {{- else if eq .Values.vector.autoscaling.type "both" }} 39 | - type: Resource 40 | resource: 41 | name: cpu 42 | target: 43 | type: Utilization 44 | averageUtilization: {{ .Values.vector.autoscaling.targets.cpu }} 45 | - type: Pods 46 | pods: 47 | metric: 48 | name: nginx_ingress_controller_requests 49 | target: 50 | type: AverageValue 51 | averageValue: {{ .Values.vector.autoscaling.targets.requestRate }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/multidim/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.multidim.enabled .Values.multidim.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ .Release.Name }}-multidim-hpa 6 | labels: 7 | app: {{ .Release.Name }}-multidim 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ .Release.Name }}-multidim 13 | minReplicas: {{ .Values.multidim.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.multidim.autoscaling.maxReplicas }} 15 | behavior: 16 | {{- with .Values.multidim.autoscaling.behavior }} 17 | scaleDown: 18 | stabilizationWindowSeconds: {{ .scaleDown.stabilizationWindowSeconds }} 19 | scaleUp: 20 | stabilizationWindowSeconds: {{ .scaleUp.stabilizationWindowSeconds }} 21 | {{- end }} 22 | metrics: 23 | {{- if eq .Values.multidim.autoscaling.type "cpu" }} 24 | - type: Resource 25 | resource: 26 | name: cpu 27 | target: 28 | type: Utilization 29 | averageUtilization: {{ .Values.multidim.autoscaling.targets.cpu }} 30 | {{- else if eq .Values.multidim.autoscaling.type "requestRate" }} 31 | - type: Pods 32 | pods: 33 | metric: 34 | name: nginx_ingress_controller_requests 35 | target: 36 | type: AverageValue 37 | averageValue: {{ .Values.multidim.autoscaling.targets.requestRate }} 38 | {{- else if eq .Values.multidim.autoscaling.type "both" }} 39 | - type: Resource 40 | resource: 41 | name: cpu 42 | target: 43 | type: Utilization 44 | averageUtilization: {{ .Values.multidim.autoscaling.targets.cpu }} 45 | - type: Pods 46 | pods: 47 | metric: 48 | name: nginx_ingress_controller_requests 49 | target: 50 | type: AverageValue 51 | averageValue: {{ .Values.multidim.autoscaling.targets.requestRate }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /charts/eoapi/templates/_helpers/core.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "eoapi.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "eoapi.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "eoapi.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "eoapi.labels" -}} 37 | helm.sh/chart: {{ include "eoapi.chart" . }} 38 | {{ include "eoapi.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "eoapi.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "eoapi.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "eoapi.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "eoapi.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /charts/postgrescluster/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for deploying a Crunchy PostgreSQL cluster! 2 | 3 | (((((((((((((((((((((( 4 | (((((((((((((%%%%%%%((((((((((((((( 5 | (((((((((((%%% %%%%(((((((((((( 6 | (((((((((((%%( (((( ( %%%((((((((((( 7 | (((((((((((((%% (( ,(( %%%((((((((((( 8 | (((((((((((((((%% *%%/ %%%%%%%(((((((((( 9 | (((((((((((((((((((%%(( %%%%%%%%%%#(((((%%%%%%%%%%#(((((((((((( 10 | ((((((((((((((((((%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%(((((((((((((( 11 | *((((((((((((((((((((%%%%%% /%%%%%%%%%%%%%%%%%%%(((((((((((((((( 12 | (((((((((((((((((((((((%%%/ .%, %%%((((((((((((((((((, 13 | ((((((((((((((((((((((% %#((((((((((((((((( 14 | (((((((((((((((%%%%%% #%((((((((((((((((( 15 | ((((((((((((((%% %%(((((((((((((((, 16 | ((((((((((((%%%#% % %%((((((((((((((( 17 | ((((((((((((%. % % #(((((((((((((( 18 | (((((((((((%% % %%* %((((((((((((( 19 | #(###(###(#%% %%% %% %%% #%%#(###(###(# 20 | ###########%%%%% /%%%%%%%%%%%%% %% %%%%% ,%%####### 21 | ###############%% %%%%%% %%% %%%%%%%% %%##### 22 | ################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %%## 23 | ################%% %%%%%%%%%%%%%%%%% %%%% % 24 | ##############%# %% (%%%%%%% %%%%%% 25 | #############% %%%%% %%%%%%%%%%% 26 | ###########% %%%%%%%%%%% %%%%%%%%% 27 | #########%% %% %%%%%%%%%%%%%%%# 28 | ########%% %% %%%%%%%%% 29 | ######%% %% %%%%%% 30 | ####%%% %%%%% % 31 | %% %%%% 32 | -------------------------------------------------------------------------------- /docs/manage-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Data Management" 3 | description: "Loading STAC collections and items into PostgreSQL using pypgstac" 4 | external_links: 5 | - name: "eoapi-k8s Repository" 6 | url: "https://github.com/developmentseed/eoapi-k8s" 7 | - name: "pypgstac Documentation" 8 | url: "https://github.com/stac-utils/pypgstac" 9 | - name: "STAC Specification" 10 | url: "https://stacspec.org/" 11 | --- 12 | 13 | # Data management 14 | 15 | eoAPI-k8s provides a basic data ingestion process that consist of manual operations on the components of the stack. 16 | 17 | # Load data 18 | 19 | You will have to have STAC records for the collection and items you wish to load (e.g., `collections.json` and `items.json`). 20 | [This repo](https://github.com/vincentsarago/MAXAR_opendata_to_pgstac) contains a few script that may help you to generate sample input data. 21 | 22 | ## Preshipped bash script 23 | 24 | Execute `make ingest` to load data into the eoAPI service - it expects `collections.json` and `items.json` in the current directory. 25 | 26 | ## Manual steps 27 | 28 | In order to add raster data to eoAPI you can load STAC collections and items into the PostgreSQL database using pgSTAC and the tool `pypgstac`. 29 | 30 | First, ensure your Kubernetes cluster is running and `kubectl` is configured to access and modify it. 31 | 32 | In a second step, you'll have to upload the data into the pod running the raster eoAPI service. You can use the following commands to copy the data: 33 | 34 | ```bash 35 | kubectl cp collections.json "$NAMESPACE/$EOAPI_POD_RASTER":/tmp/collections.json 36 | kubectl cp items.json "$NAMESPACE/$EOAPI_POD_RASTER":/tmp/items.json 37 | ``` 38 | Then, bash into the pod or server running the raster eoAPI service, you can use the following commands to load the data: 39 | 40 | ```bash 41 | #!/bin/bash 42 | apt update -y && apt install python3 python3-pip -y && pip install pypgstac[psycopg]'; 43 | pypgstac pgready --dsn $PGADMIN_URI 44 | pypgstac load collections /tmp/collections.json --dsn $PGADMIN_URI --method insert_ignore 45 | pypgstac load items /tmp/items.json --dsn $PGADMIN_URI --method insert_ignore 46 | ``` 47 | -------------------------------------------------------------------------------- /charts/eoapi/templates/_helpers/validation.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | values.schema.json doesn't play nice combined value checks 3 | so we use this helper function to check autoscaling rules 4 | */}} 5 | {{- define "eoapi.validateAutoscaleRules" -}} 6 | {{- if and .Values.ingress.enabled (ne .Values.ingress.className "nginx") }} 7 | {{/* "requestRate" cannot be enabled for any service if not "nginx" so give feedback and fail */}} 8 | {{- $requestRateEnabled := false }} 9 | {{- range .Values.apiServices }} 10 | {{- if and (index $.Values . "autoscaling" "enabled") (eq (index $.Values . "autoscaling" "type") "requestRate") }} 11 | {{- $requestRateEnabled = true }} 12 | {{- end }} 13 | {{- end }} 14 | {{- if $requestRateEnabled }} 15 | {{- fail "When using an 'ingress.className' other than 'nginx' you cannot enable autoscaling by 'requestRate' at this time b/c it's solely an nginx metric" }} 16 | {{- end }} 17 | {{/* "both" cannot be enabled for any service if not "nginx" so give feedback and fail */}} 18 | {{- $bothEnabled := false }} 19 | {{- range .Values.apiServices }} 20 | {{- if and (index $.Values . "autoscaling" "enabled") (eq (index $.Values . "autoscaling" "type") "both") }} 21 | {{- $bothEnabled = true }} 22 | {{- end }} 23 | {{- end }} 24 | {{- if $bothEnabled }} 25 | {{- fail "When using an 'ingress.className' other than 'nginx' you cannot enable autoscaling by 'both' at this time b/c 'requestRate' is solely an nginx metric" }} 26 | {{- end }} 27 | {{- end }} 28 | {{- end -}} 29 | 30 | {{/* 31 | Validate stac-auth-proxy configuration 32 | Ensures OIDC_DISCOVERY_URL is set when stac-auth-proxy is enabled 33 | Ensures stac-auth-proxy cannot be enabled when stac is disabled 34 | */}} 35 | {{- define "eoapi.validateStacAuthProxy" -}} 36 | {{- if index .Values "stac-auth-proxy" "enabled" }} 37 | {{- if not .Values.stac.enabled }} 38 | {{- fail "stac-auth-proxy cannot be enabled when stac.enabled is false. Enable stac first or disable stac-auth-proxy." }} 39 | {{- end }} 40 | {{- if not (index .Values "stac-auth-proxy" "env" "OIDC_DISCOVERY_URL") }} 41 | {{- fail "stac-auth-proxy.env.OIDC_DISCOVERY_URL is required when stac-auth-proxy is enabled. Set it to your OpenID Connect discovery URL (e.g., https://your-auth-server/.well-known/openid-configuration)" }} 42 | {{- end }} 43 | {{- end }} 44 | {{- end -}} 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eoapi-k8s 2 | 3 |

4 | eoapi-k8s 5 |

6 | 7 | [eoAPI](https://eoapi.dev/) is a progressive platform for hosting Earth Observation data. It offers a suite of APIs (OGC and STAC-based) for data access and analysis. This repository includes a production-ready Kubernetes deployment with flexible database options, unified ingress configuration, and built-in monitoring. 8 | 9 |

10 | 11 | Test 12 | 13 | 14 | License 15 | 16 | 17 | Artifact Hub 18 | 19 |

20 | 21 | ## Prerequisites 22 | 23 | - Kubernetes cluster (1.21+) 24 | - Helm 3.x 25 | - `kubectl` configured for cluster access 26 | 27 | ## Documentation 28 | 29 | ### Get started 30 | 31 | * [General eoAPI documentation](https://eoapi.dev). 32 | * [eoAPI-k8s documentation](https://eoapi.dev/deployment/kubernetes) 33 | 34 | ## Contributing 35 | 36 | * **We would :heart: to hear from you!** Please [join the discussion](https://github.com/developmentseed/eoAPI/discussions/209) and let us know how you're using eoAPI! This helps us improve the project for you and others. If you prefer to remain anonymous, you can email us at eoapi@developmentseed.org, and we'll be happy to post a summary on your behalf. 37 | 38 | * **We welcome contributions** from the community! Feel free to open an issue or submit a pull request. 39 | 40 | * **Please ensure:** that pull requests make an update to `CHANGELOG.md` and that your pull request **title** adheres to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 41 | 42 | ## License 43 | 44 | This project is licensed under the [MIT License](./LICENSE). 45 | -------------------------------------------------------------------------------- /charts/postgrescluster/README.md: -------------------------------------------------------------------------------- 1 | # PostgresCluster Helm Chart 2 | 3 | A Helm chart wrapper for deploying PostgreSQL clusters using [CrunchyData's PostgreSQL Operator (PGO)](https://access.crunchydata.com/documentation/postgres-operator/). 4 | 5 | ## Purpose 6 | 7 | This chart creates `PostgresCluster` custom resources that are managed by PGO. It serves as a configuration layer between eoAPI and the PostgreSQL operator, providing: 8 | 9 | - **PostGIS support** for geospatial data 10 | - **PgBouncer** connection pooling 11 | - **Configurable backups** (S3, GCS, Azure, or volume-based) 12 | - **eoAPI-specific defaults** for schema permissions and database setup 13 | 14 | ## Prerequisites 15 | 16 | Install the PostgreSQL Operator first: 17 | 18 | ```bash 19 | helm install --set disable_check_for_upgrades=true pgo \ 20 | oci://registry.developers.crunchydata.com/crunchydata/pgo \ 21 | --version 5.7.4 22 | ``` 23 | 24 | ## Usage 25 | 26 | This chart is typically used as a dependency of the main eoAPI chart: 27 | 28 | ```yaml 29 | # In eoAPI's Chart.yaml 30 | dependencies: 31 | - name: postgrescluster 32 | version: 5.7.4 33 | repository: "https://devseed.com/eoapi-k8s/" 34 | condition: postgrescluster.enabled 35 | ``` 36 | 37 | ### Standalone Installation 38 | 39 | ```bash 40 | helm install my-postgres ./charts/postgrescluster \ 41 | --set postgresVersion=16 \ 42 | --set postGISVersion=3.4 43 | ``` 44 | 45 | ## Key Configuration 46 | 47 | | Parameter | Description | Default | 48 | |-----------|-------------|---------| 49 | | `postgresVersion` | PostgreSQL version | `16` | 50 | | `postGISVersion` | PostGIS version | `3.4` | 51 | | `pgBouncerReplicas` | Number of PgBouncer instances | `1` | 52 | | `backupsEnabled` | Enable pgBackRest backups | `false` | 53 | | `instances` | PostgreSQL instance configuration | See values.yaml | 54 | 55 | ### Instance Configuration 56 | 57 | ```yaml 58 | instances: 59 | - name: postgres 60 | replicas: 2 # High availability with 2 replicas 61 | dataVolumeClaimSpec: 62 | accessModes: ["ReadWriteOnce"] 63 | resources: 64 | requests: 65 | storage: 10Gi 66 | ``` 67 | 68 | ### Backup Configuration 69 | 70 | ```yaml 71 | backupsEnabled: true 72 | s3: 73 | bucket: "my-backups" 74 | endpoint: "s3.amazonaws.com" 75 | region: "us-east-1" 76 | key: "ACCESS_KEY_ID" 77 | keySecret: "SECRET_ACCESS_KEY" 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/docs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "structure": "flat", 3 | "assets_dir": "images", 4 | "external_repo": "https://github.com/developmentseed/eoapi-k8s", 5 | "files": { 6 | "index.md": { "title": "Kubernetes Deployment", "slug": "index" }, 7 | "quick-start.md": { "title": "Quick Start", "slug": "quick-start" }, 8 | "helm-install.md": { "title": "Helm Installation", "slug": "helm-install" }, 9 | "configuration.md": { "title": "Configuration Options", "slug": "configuration" }, 10 | "unified-ingress.md": { "title": "Unified Ingress", "slug": "unified-ingress" }, 11 | "aws-eks.md": { "title": "AWS EKS Setup", "slug": "aws-eks" }, 12 | "gcp-gke.md": { "title": "GCP GKE Setup", "slug": "gcp-gke" }, 13 | "azure.md": { "title": "Azure AKS Setup", "slug": "azure" }, 14 | "manage-data.md": { "title": "Data Management", "slug": "manage-data" }, 15 | "autoscaling.md": { "title": "Autoscaling & Monitoring", "slug": "autoscaling" }, 16 | "stac-auth-proxy.md": { "title": "STAC Auth Proxy", "slug": "stac-auth-proxy" }, 17 | "release.md": { "title": "Release Workflow", "slug": "release" }, 18 | "README.md": { "title": "Documentation Guide", "slug": "docs-readme" } 19 | }, 20 | "nav_structure": [ 21 | { "title": "Overview", "file": "index.md" }, 22 | { "title": "Quick Start", "file": "quick-start.md" }, 23 | { 24 | "title": "Installation", 25 | "children": [ 26 | { "title": "Helm Installation", "file": "helm-install.md" }, 27 | { "title": "Configuration Options", "file": "configuration.md" }, 28 | { "title": "Unified Ingress", "file": "unified-ingress.md" } 29 | ] 30 | }, 31 | { 32 | "title": "Cloud Providers", 33 | "children": [ 34 | { "title": "AWS EKS", "file": "aws-eks.md" }, 35 | { "title": "GCP GKE", "file": "gcp-gke.md" }, 36 | { "title": "Azure AKS", "file": "azure.md" } 37 | ] 38 | }, 39 | { 40 | "title": "Operations", 41 | "children": [ 42 | { "title": "Data Management", "file": "manage-data.md" }, 43 | { "title": "Autoscaling & Monitoring", "file": "autoscaling.md" } 44 | ] 45 | }, 46 | { 47 | "title": "Advanced", 48 | "children": [ 49 | { "title": "STAC Auth Proxy", "file": "stac-auth-proxy.md" }, 50 | { "title": "Release Workflow", "file": "release.md" } 51 | ] 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/doc-server.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.docServer.enabled}} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Release.Name }}-doc-server-html 6 | data: 7 | index.html: | 8 | 9 | 10 | eoAPI 11 | 12 | 13 |

This is the root path /

14 |

Your service configuration is using path rewrites. So use these paths for each service:

15 | 24 | 25 | 26 | --- 27 | apiVersion: apps/v1 28 | kind: Deployment 29 | metadata: 30 | name: {{ .Release.Name }}-doc-server 31 | spec: 32 | replicas: 1 33 | selector: 34 | matchLabels: 35 | app: {{ .Release.Name }}-doc-server 36 | template: 37 | metadata: 38 | labels: 39 | app: {{ .Release.Name }}-doc-server 40 | spec: 41 | containers: 42 | - name: doc-server 43 | image: nginx:alpine 44 | volumeMounts: 45 | - name: {{ .Release.Name }}-doc-html 46 | mountPath: /usr/share/nginx/html 47 | ports: 48 | - containerPort: 80 49 | volumes: 50 | - name: {{ .Release.Name }}-doc-html 51 | configMap: 52 | name: {{ .Release.Name }}-doc-server-html 53 | {{- if .Values.docServer.settings }} 54 | {{- with .Values.docServer.settings.affinity }} 55 | affinity: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.docServer.settings.tolerations }} 59 | tolerations: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | {{- end }} 63 | --- 64 | apiVersion: v1 65 | kind: Service 66 | metadata: 67 | name: {{ .Release.Name }}-doc-server 68 | spec: 69 | selector: 70 | app: {{ .Release.Name }}-doc-server 71 | ports: 72 | - protocol: TCP 73 | port: 80 74 | targetPort: 80 75 | --- 76 | {{- end }} 77 | -------------------------------------------------------------------------------- /charts/eoapi/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for installing {{ .Chart.Name }} {{ .Chart.Version }} 2 | 3 | Your eoAPI deployment is now being set up. This may take a few minutes. 4 | 5 | {{- if .Values.ingress.enabled }} 6 | 7 | You can access the services at: 8 | {{- if .Values.ingress.host }} 9 | Host: {{ .Values.ingress.host }} 10 | {{- else }} 11 | Get the host using: 12 | $ kubectl get ingress -n {{ .Release.Namespace }} 13 | {{- end }} 14 | 15 | Available endpoints: 16 | {{- if has "stac" .Values.apiServices }} 17 | - STAC API: {{ if .Values.ingress.host }}https://{{ .Values.ingress.host }}{{ end }}/stac 18 | {{- end }} 19 | {{- if has "raster" .Values.apiServices }} 20 | - Raster API: {{ if .Values.ingress.host }}https://{{ .Values.ingress.host }}{{ end }}/raster 21 | {{- end }} 22 | {{- if has "vector" .Values.apiServices }} 23 | - Vector API: {{ if .Values.ingress.host }}https://{{ .Values.ingress.host }}{{ end }}/vector 24 | {{- end }} 25 | {{- if has "multidim" .Values.apiServices }} 26 | - MultiDim API: {{ if .Values.ingress.host }}https://{{ .Values.ingress.host }}{{ end }}/multidim 27 | {{- end }} 28 | {{- if .Values.browser.enabled }} 29 | - STAC Browser: {{ if .Values.ingress.host }}https://{{ .Values.ingress.host }}{{ end }}/browser 30 | {{- end }} 31 | 32 | {{- else }} 33 | You have disabled the ingress. To access the services, you need to: 34 | 1. Set up your own ingress controller, or 35 | 2. Use port forwarding: 36 | 37 | {{- if has "stac" .Values.apiServices }} 38 | $ kubectl port-forward -n {{ .Release.Namespace }} svc/stac 8080:{{ .Values.service.port }} 39 | {{- end }} 40 | {{- if has "raster" .Values.apiServices }} 41 | $ kubectl port-forward -n {{ .Release.Namespace }} svc/raster 8081:{{ .Values.service.port }} 42 | {{- end }} 43 | {{- if has "vector" .Values.apiServices }} 44 | $ kubectl port-forward -n {{ .Release.Namespace }} svc/vector 8082:{{ .Values.service.port }} 45 | {{- end }} 46 | {{- if has "multidim" .Values.apiServices }} 47 | $ kubectl port-forward -n {{ .Release.Namespace }} svc/multidim 8083:{{ .Values.service.port }} 48 | {{- end }} 49 | {{- end }} 50 | 51 | To verify the deployment status: 52 | $ kubectl get pods -n {{ .Release.Namespace }} 53 | 54 | For troubleshooting: 55 | $ kubectl describe pods -n {{ .Release.Namespace }} 56 | $ kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/name={{ .Chart.Name }} 57 | 58 | Visit https://github.com/developmentseed/eoapi-k8s for more information. 59 | -------------------------------------------------------------------------------- /charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test stac-auth-proxy ingress routing 2 | templates: 3 | - networking/ingress.yaml 4 | 5 | tests: 6 | - it: should route ingress to stac-auth-proxy when enabled 7 | set: 8 | ingress.enabled: true 9 | ingress.className: nginx 10 | stac.enabled: true 11 | stac.ingress.enabled: true 12 | stac.ingress.path: "/stac" 13 | stac-auth-proxy.enabled: true 14 | service.port: 8080 15 | asserts: 16 | - contains: 17 | path: spec.rules[0].http.paths 18 | content: 19 | pathType: ImplementationSpecific 20 | path: /stac(/|$)(.*) 21 | backend: 22 | service: 23 | name: RELEASE-NAME-stac-auth-proxy 24 | port: 25 | number: 8080 26 | template: networking/ingress.yaml 27 | 28 | - it: should route ingress directly to stac when auth-proxy is disabled 29 | set: 30 | ingress.enabled: true 31 | ingress.className: nginx 32 | stac.enabled: true 33 | stac.ingress.enabled: true 34 | stac.ingress.path: "/stac" 35 | stac-auth-proxy.enabled: false 36 | service.port: 8080 37 | asserts: 38 | - contains: 39 | path: spec.rules[0].http.paths 40 | content: 41 | pathType: ImplementationSpecific 42 | path: /stac(/|$)(.*) 43 | backend: 44 | service: 45 | name: RELEASE-NAME-stac 46 | port: 47 | number: 8080 48 | template: networking/ingress.yaml 49 | 50 | - it: should not create stac routes when stac is disabled 51 | set: 52 | ingress.enabled: true 53 | stac.enabled: false 54 | stac-auth-proxy.enabled: true 55 | asserts: 56 | - notContains: 57 | path: spec.rules[0].http.paths 58 | any: true 59 | content: 60 | path: /stac(/|$)(.*) 61 | template: networking/ingress.yaml 62 | 63 | - it: should route correctly with experimental profile 64 | values: 65 | - ../profiles/experimental.yaml 66 | set: 67 | ingress.enabled: true 68 | asserts: 69 | - contains: 70 | path: spec.rules[0].http.paths 71 | content: 72 | pathType: ImplementationSpecific 73 | path: /stac(/|$)(.*) 74 | backend: 75 | service: 76 | name: RELEASE-NAME-stac-auth-proxy 77 | port: 78 | number: 8080 79 | template: networking/ingress.yaml 80 | -------------------------------------------------------------------------------- /docs/helm-install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Manual Helm Installation" 3 | description: "Step-by-step Helm deployment process with custom configurations" 4 | external_links: 5 | - name: "eoapi-k8s Repository" 6 | url: "https://github.com/developmentseed/eoapi-k8s" 7 | - name: "PostgreSQL Operator Documentation" 8 | url: "https://access.crunchydata.com/documentation/postgres-operator/" 9 | - name: "Helm Charts Repository" 10 | url: "https://devseed.com/eoapi-k8s/" 11 | --- 12 | 13 | # Manual Helm Install 14 | 15 | 0. `eoapi-k8s` depends on the [Crunchydata Postgresql Operator](https://access.crunchydata.com/documentation/postgres-operator/latest/installation/helm). Install that first: 16 | 17 | ```bash 18 | # Check latest version at: https://github.com/CrunchyData/postgres-operator/releases 19 | $ helm install --set disable_check_for_upgrades=true pgo oci://registry.developers.crunchydata.com/crunchydata/pgo --version 5.7.0 20 | ``` 21 | 22 | 1. Add the eoapi repo from https://devseed.com/eoapi-k8s/: 23 | 24 | ```bash 25 | $ helm repo add eoapi https://devseed.com/eoapi-k8s/ 26 | ``` 27 | 28 | 2. List out the eoapi chart versions 29 | 30 | ```bash 31 | $ helm search repo eoapi --versions 32 | # Use latest stable version from output above 33 | ``` 34 | 35 | 3. Optionally override keys/values in the default `values.yaml` with a custom `config.yaml` like below: 36 | 37 | ```bash 38 | $ cat config.yaml 39 | vector: 40 | enable: false 41 | pgstacBootstrap: 42 | settings: 43 | envVars: 44 | LOAD_FIXTURES: "0" 45 | RUN_FOREVER: "1" 46 | ``` 47 | 48 | 4. Then `helm install` with those `config.yaml` values: 49 | 50 | ```bash 51 | # Replace VERSION with latest from `helm search repo eoapi` 52 | $ export CHART_VERSION=$(helm search repo eoapi/eoapi --versions | head -2 | tail -1 | awk '{print $2}') 53 | $ helm install -n eoapi --create-namespace eoapi eoapi/eoapi --version $CHART_VERSION -f config.yaml 54 | ``` 55 | 56 | 5. or check out this repo and `helm install` from this repo's `charts/` folder: 57 | 58 | ```bash 59 | ###################################################### 60 | # create os environment variables for required secrets 61 | ###################################################### 62 | $ export GITSHA=$(git rev-parse HEAD | cut -c1-10) 63 | 64 | $ cd ./charts 65 | 66 | $ helm install \ 67 | --namespace eoapi \ 68 | --create-namespace \ 69 | --set gitSha=$GITSHA \ 70 | eoapi \ 71 | ./eoapi 72 | ``` 73 | -------------------------------------------------------------------------------- /.github/workflows/stac-browser.yml: -------------------------------------------------------------------------------- 1 | name: Build STAC Browser 2 | 3 | on: 4 | release: 5 | types: [released] 6 | workflow_dispatch: 7 | inputs: 8 | TAG_NAME: 9 | description: "Tag name for this image" 10 | required: true 11 | default: "eoapi-k8s-stac-browser" 12 | STAC_BROWSER_VERSION: 13 | description: "STAC Browser version to build (e.g. v3.3.4)" 14 | required: true 15 | default: "v3.3.4" 16 | 17 | env: 18 | REGISTRY: ghcr.io 19 | TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} 20 | 21 | jobs: 22 | build-and-push: 23 | runs-on: ubuntu-latest 24 | name: Build and push STAC Browser image 25 | 26 | steps: 27 | - name: Checkout STAC Browser repository 28 | uses: actions/checkout@v6 29 | with: 30 | repository: radiantearth/stac-browser 31 | ref: ${{ github.event.inputs.STAC_BROWSER_VERSION }} 32 | 33 | - name: Set environment variables 34 | run: | 35 | { 36 | echo "VERSION=${TAG_NAME#v}" 37 | echo "IMAGE_NAME=$REGISTRY/${GITHUB_REPOSITORY,,}/stac-browser" 38 | echo "COMMITED_AT=$(git show -s --format=%cI "$(git rev-parse HEAD)")" 39 | echo "REVISION=$(git rev-parse --short HEAD)" 40 | } >> "$GITHUB_ENV" 41 | 42 | - name: Collect Docker image metadata 43 | id: meta 44 | uses: docker/metadata-action@v5 45 | with: 46 | images: ${{ env.IMAGE_NAME }} 47 | labels: | 48 | org.opencontainers.image.created=${{ env.COMMITED_AT }} 49 | org.opencontainers.image.version=v${{ env.VERSION }} 50 | org.opencontainers.image.maintainer=${{ github.repository_owner }} 51 | tags: | 52 | type=semver,pattern={{version}},value=v${{ env.VERSION }} 53 | 54 | - name: Log in to the GitHub container registry 55 | uses: docker/login-action@v3 56 | with: 57 | registry: ${{ env.REGISTRY }} 58 | username: ${{ github.repository_owner }} 59 | password: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | - name: Build and push Docker image 62 | uses: docker/build-push-action@v6 63 | with: 64 | context: . 65 | push: true 66 | build-args: | 67 | VERSION=${{ env.VERSION }} 68 | REVISION=${{ env.REVISION }} 69 | pathPrefix=/browser/ 70 | tags: ${{ steps.meta.outputs.tags }} 71 | labels: ${{ steps.meta.outputs.labels }} 72 | cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:edge 73 | cache-to: type=inline 74 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | HELM_VERSION: v3.15.2 11 | PGO_VERSION: 5.7.4 12 | 13 | jobs: 14 | fast-checks: 15 | name: Simple tests 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: actions/setup-node@v6 20 | with: 21 | node-version: '24' 22 | 23 | - name: Install Helm 24 | uses: azure/setup-helm@v4 25 | with: 26 | version: ${{ env.HELM_VERSION }} 27 | 28 | - name: Run linters 29 | run: ./eoapi-cli test lint 30 | 31 | - name: Validate Helm values schema 32 | run: ./eoapi-cli test schema 33 | 34 | - name: Run Helm unit tests 35 | run: ./eoapi-cli test unit 36 | 37 | integration-tests: 38 | name: Integration tests 39 | needs: fast-checks 40 | if: github.event.pull_request.head.repo.full_name == github.repository 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v6 44 | - uses: actions/setup-python@v6 45 | with: 46 | python-version: '3.14' 47 | 48 | - name: Set release name 49 | run: echo "RELEASE_NAME=eoapi-$(echo "${{ github.sha }}" | cut -c1-8)" >> "$GITHUB_ENV" 50 | 51 | - name: Start K3s cluster 52 | uses: jupyterhub/action-k3s-helm@v4 53 | with: 54 | k3s-channel: latest 55 | helm-version: ${{ env.HELM_VERSION }} 56 | metrics-enabled: true 57 | docker-enabled: true 58 | 59 | - name: Wait until cluster is ready 60 | run: ./eoapi-cli cluster wait-ready 61 | 62 | - name: Deploy eoAPI 63 | run: ./eoapi-cli deployment run 64 | 65 | - name: Run integration tests 66 | run: ./eoapi-cli test integration 67 | 68 | - name: Run notification tests 69 | run: ./eoapi-cli test notification 70 | 71 | - name: Run autoscaling tests 72 | run: ./eoapi-cli test autoscaling 73 | 74 | - name: Debug failed deployment 75 | if: failure() 76 | run: ./eoapi-cli deployment debug 77 | 78 | - name: Cleanup 79 | if: always() 80 | run: | 81 | helm uninstall "$RELEASE_NAME" -n eoapi || true 82 | kubectl delete namespace eoapi || true 83 | 84 | validate-docs: 85 | name: Validate documentation 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v6 89 | - uses: actions/setup-node@v6 90 | with: 91 | node-version: '24' 92 | - uses: actions/setup-python@v6 93 | with: 94 | python-version: '3.14' 95 | 96 | - name: Check documentation 97 | run: ./eoapi-cli docs check 98 | -------------------------------------------------------------------------------- /scripts/test/notification.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 6 | source "${SCRIPT_DIR}/../lib/common.sh" 7 | 8 | NAMESPACE="${NAMESPACE:-eoapi}" 9 | RELEASE_NAME="${RELEASE_NAME:-}" 10 | DEBUG_MODE="${DEBUG_MODE:-false}" 11 | 12 | run_notification_tests() { 13 | local pytest_args="${1:-}" 14 | 15 | log_info "Running notification tests..." 16 | 17 | check_requirements python3 kubectl || return 1 18 | 19 | if [[ -z "$RELEASE_NAME" ]]; then 20 | RELEASE_NAME=$(kubectl get deployments -n "$NAMESPACE" -o jsonpath='{.items[?(@.metadata.labels.app\.kubernetes\.io/name=="eoapi")].metadata.labels.app\.kubernetes\.io/instance}' | head -1) 21 | [[ -z "$RELEASE_NAME" ]] && { log_error "Cannot detect release name"; return 1; } 22 | fi 23 | 24 | log_debug "Connected to cluster: $(kubectl config current-context)" 25 | 26 | log_info "Installing Python test dependencies..." 27 | python3 -m pip install --quiet pytest httpx requests >/dev/null 2>&1 28 | 29 | # Set up service endpoints for API access 30 | # Use existing endpoints if set, otherwise determine based on cluster access 31 | if [[ -z "${STAC_ENDPOINT:-}" ]]; then 32 | # Check if we have an ingress 33 | local ingress_host 34 | ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "") 35 | 36 | if [[ -n "$ingress_host" ]]; then 37 | # Use ingress host 38 | export STAC_ENDPOINT="http://${ingress_host}/stac" 39 | export RASTER_ENDPOINT="http://${ingress_host}/raster" 40 | export VECTOR_ENDPOINT="http://${ingress_host}/vector" 41 | export MOCK_OIDC_ENDPOINT="http://${ingress_host}/mock-oidc" 42 | else 43 | # Fall back to localhost (assumes port-forward or local ingress) 44 | export STAC_ENDPOINT="http://localhost/stac" 45 | export RASTER_ENDPOINT="http://localhost/raster" 46 | export VECTOR_ENDPOINT="http://localhost/vector" 47 | export MOCK_OIDC_ENDPOINT="http://localhost/mock-oidc" 48 | fi 49 | fi 50 | export NAMESPACE 51 | export RELEASE_NAME 52 | 53 | log_info "Running notification tests..." 54 | 55 | local cmd="python3 -m pytest tests/notification" 56 | [[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd -v --tb=short" 57 | [[ -n "$pytest_args" ]] && cmd="$cmd $pytest_args" 58 | 59 | log_debug "Running: $cmd" 60 | 61 | if eval "$cmd"; then 62 | log_success "Notification tests passed" 63 | return 0 64 | else 65 | log_error "Notification tests failed" 66 | return 1 67 | fi 68 | } 69 | 70 | run_notification_tests "$@" 71 | -------------------------------------------------------------------------------- /docs/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick Start" 3 | description: "Fast installation guide for eoAPI Kubernetes deployment" 4 | external_links: 5 | - name: "eoapi-k8s Repository" 6 | url: "https://github.com/developmentseed/eoapi-k8s" 7 | - name: "Helm Documentation" 8 | url: "https://helm.sh/docs/" 9 | --- 10 | 11 | # Quick Start 12 | 13 | ## Prerequisites 14 | 15 | - [helm](https://helm.sh/docs/intro/install/) 16 | - A Kubernetes cluster (local or cloud-based) 17 | - `kubectl` configured for your cluster (ensure `KUBECONFIG` environment variable is set to point to your cluster configuration file, or use `kubectl config use-context ` to set the active cluster) 18 | - [helm unittest](https://github.com/helm-unittest/helm-unittest?tab=readme-ov-file#install) if contributing to the repository and running `./eoapi-cli test unit` 19 | 20 | ## Option 1: One-Command Installation 21 | 22 | The fastest way to get started is using our eoAPI CLI: 23 | 24 | For local development with k3s/k3d: 25 | ```bash 26 | ./eoapi-cli cluster start 27 | ./eoapi-cli deployment run 28 | ``` 29 | 30 | For cloud deployment: 31 | ```bash 32 | ./eoapi-cli deployment run 33 | ``` 34 | 35 | This will automatically: 36 | 1. Install the PostgreSQL operator 37 | 2. Add the eoAPI helm repository 38 | 3. Install the eoAPI helm chart 39 | 4. Set up necessary namespaces and configurations 40 | 41 | > [!WARNING] 42 | > Some images do not provide a `linux/arm64` compatible download (You may see image pull failures) which causes failures on M1 etc Macs, to get around this, you can pre-pull the image with: 43 | > ``` 44 | > docker pull --platform=linux/amd64 45 | > minikube image load 46 | > ``` 47 | > You can then re-deploy the service and it will now use the local image. 48 | 49 | ## Option 2: Step-by-Step Installation 50 | 51 | If you prefer more control over the installation process: 52 | 53 | 1. Install the PostgreSQL operator: 54 | ```bash 55 | helm upgrade --install \ 56 | --set disable_check_for_upgrades=true pgo \ 57 | oci://registry.developers.crunchydata.com/crunchydata/pgo \ 58 | --version 5.7.4 59 | ``` 60 | 61 | 2. Add the eoAPI helm repository: 62 | ```bash 63 | helm repo add eoapi https://devseed.com/eoapi-k8s/ 64 | ``` 65 | 66 | 3. Get your current git SHA: 67 | ```bash 68 | export GITSHA=$(git rev-parse HEAD | cut -c1-10) 69 | ``` 70 | 71 | 4. Install eoAPI: 72 | ```bash 73 | helm upgrade --install \ 74 | --namespace eoapi \ 75 | --create-namespace \ 76 | --set gitSha=$GITSHA \ 77 | eoapi devseed/eoapi 78 | ``` 79 | 80 | ### Post-Installation 81 | 82 | 1. Enable ingress (for Minikube only - k3s has Traefik built-in): 83 | ```bash 84 | minikube addons enable ingress 85 | ``` 86 | 87 | 2. Optional: Load sample data: 88 | ```bash 89 | ./eoapi-cli ingest collections.json items.json 90 | ``` 91 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Pre-commit configuration for eoAPI Kubernetes charts 2 | # Fast, essential checks only - heavy validation moved to CI 3 | 4 | repos: 5 | # Basic file formatting and validation 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v6.0.0 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | - id: check-yaml 12 | args: ['--allow-multiple-documents'] 13 | exclude: ^charts/.+/templates/ 14 | - id: check-added-large-files 15 | 16 | # YAML linting 17 | - repo: https://github.com/adrienverge/yamllint 18 | rev: v1.37.1 19 | hooks: 20 | - id: yamllint 21 | files: \.(yaml|yml)$ 22 | exclude: ^charts/.+/templates/ 23 | 24 | # Shell script validation 25 | - repo: https://github.com/shellcheck-py/shellcheck-py 26 | rev: v0.11.0.1 27 | hooks: 28 | - id: shellcheck 29 | args: ['-e', 'SC1091'] 30 | 31 | # GitHub Actions linting 32 | - repo: https://github.com/rhysd/actionlint 33 | rev: v1.7.7 34 | hooks: 35 | - id: actionlint 36 | 37 | # Python type checking for test files 38 | - repo: https://github.com/pre-commit/mirrors-mypy 39 | rev: v1.11.2 40 | hooks: 41 | - id: mypy 42 | name: mypy strict mode for tests 43 | args: ['--strict', '--ignore-missing-imports'] 44 | files: ^tests\/integration\/.*\.py$ 45 | additional_dependencies: ['types-psycopg2', 'httpx', 'pytest', 'types-requests'] 46 | 47 | # Fast Helm syntax check only 48 | - repo: local 49 | hooks: 50 | - id: helm-lint-syntax 51 | name: Helm Syntax Check 52 | entry: > 53 | bash -c 'for chart in charts/*/; do 54 | if [ -f "$chart/Chart.yaml" ]; then 55 | if grep -q "dependencies:" "$chart/Chart.yaml" 2>/dev/null; then 56 | if [ -d "$chart/charts" ] && [ -n "$(find "$chart/charts" -name "*.tgz" 2>/dev/null)" ]; then 57 | echo "Linting $chart (dependencies available)..."; 58 | helm lint "$chart" --strict || exit 1; 59 | else echo "Skipping $chart (dependencies not built - run make deploy or scripts/helm-setup.sh first)"; 60 | fi; else echo "Linting $chart (no dependencies)..."; 61 | helm lint "$chart" --strict || exit 1; fi; fi; done' 62 | language: system 63 | files: ^charts/.+\.(yaml|yml)$ 64 | pass_filenames: false 65 | 66 | - id: helm-schema-validation 67 | name: Helm Schema Validation 68 | entry: ./eoapi-cli test schema 69 | language: system 70 | files: ^charts/.+\.(json|yaml|yml)$ 71 | pass_filenames: false 72 | 73 | # Exclude directories 74 | exclude: | 75 | (?x)^( 76 | \.git/.*| 77 | charts/.+/charts/.*| 78 | charts/.+/tmpcharts.*/.* 79 | )$ 80 | -------------------------------------------------------------------------------- /charts/eoapi/templates/core/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.apiServices }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: eoapi-role-{{ $.Release.Name }} 6 | labels: 7 | app: eoapi-{{ $.Release.Name }} 8 | rules: 9 | - apiGroups: ["batch"] 10 | resources: ["jobs"] 11 | verbs: ["get", "list", "watch"] 12 | --- 13 | {{- if .Values.knative.enabled }} 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRole 16 | metadata: 17 | name: eoapi-cluster-role-{{ $.Release.Name }} 18 | labels: 19 | app: eoapi-{{ $.Release.Name }} 20 | rules: 21 | # CRD management for Knative operator installation 22 | - apiGroups: ["apiextensions.k8s.io"] 23 | resources: ["customresourcedefinitions"] 24 | verbs: ["get", "list", "create", "update", "patch", "watch"] 25 | # Core resources needed by Knative operator 26 | - apiGroups: [""] 27 | resources: ["pods", "namespaces", "services", "configmaps", "secrets", "serviceaccounts"] 28 | verbs: ["get", "list", "create", "update", "patch", "watch"] 29 | # Deployment and app resources 30 | - apiGroups: ["apps"] 31 | resources: ["deployments", "replicasets"] 32 | verbs: ["get", "list", "create", "update", "patch", "watch"] 33 | # RBAC resources needed by Knative operator 34 | - apiGroups: ["rbac.authorization.k8s.io"] 35 | resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"] 36 | verbs: ["get", "list", "create", "update", "patch", "watch"] 37 | # Admission controller resources 38 | - apiGroups: ["admissionregistration.k8s.io"] 39 | resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] 40 | verbs: ["get", "list", "create", "update", "patch", "watch"] 41 | # Knative operator resources 42 | - apiGroups: ["operator.knative.dev"] 43 | resources: ["knativeservings", "knativeeventings"] 44 | verbs: ["get", "list", "create", "update", "patch", "watch"] 45 | # Allow getting cluster info for operator installation 46 | - apiGroups: [""] 47 | resources: ["nodes"] 48 | verbs: ["get", "list"] 49 | --- 50 | apiVersion: rbac.authorization.k8s.io/v1 51 | kind: ClusterRoleBinding 52 | metadata: 53 | name: eoapi-cluster-rolebinding-{{ $.Release.Name }} 54 | labels: 55 | app: eoapi-{{ $.Release.Name }} 56 | subjects: 57 | - kind: ServiceAccount 58 | name: {{ include "eoapi.serviceAccountName" . }} 59 | namespace: {{ $.Release.Namespace }} 60 | roleRef: 61 | kind: ClusterRole 62 | name: eoapi-cluster-role-{{ $.Release.Name }} 63 | apiGroup: rbac.authorization.k8s.io 64 | --- 65 | {{- end }} 66 | --- 67 | apiVersion: rbac.authorization.k8s.io/v1 68 | kind: RoleBinding 69 | metadata: 70 | name: eoapi-rolebinding-{{ $.Release.Name }} 71 | labels: 72 | app: eoapi-{{ $.Release.Name }} 73 | subjects: 74 | - kind: ServiceAccount 75 | name: {{ include "eoapi.serviceAccountName" . }} 76 | namespace: {{ $.Release.Namespace }} 77 | roleRef: 78 | kind: Role 79 | name: eoapi-role-{{ $.Release.Name }} 80 | apiGroup: rbac.authorization.k8s.io 81 | {{- end }} 82 | -------------------------------------------------------------------------------- /charts/eoapi/tests/stac_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: stac service tests 2 | templates: 3 | - templates/services/stac/deployment.yaml 4 | - templates/services/stac/configmap.yaml 5 | - templates/services/stac/service.yaml 6 | - templates/services/stac/hpa.yaml 7 | tests: 8 | - it: "stac deployment defaults" 9 | set: 10 | raster.enabled: false 11 | stac.enabled: true 12 | vector.enabled: false 13 | multidim.enabled: false 14 | gitSha: "ABC123" 15 | template: templates/services/stac/deployment.yaml 16 | asserts: 17 | - isKind: 18 | of: Deployment 19 | - matchRegex: 20 | path: metadata.name 21 | pattern: ^RELEASE-NAME-stac$ 22 | - equal: 23 | path: spec.strategy.type 24 | value: "RollingUpdate" 25 | - equal: 26 | path: spec.template.spec.containers[0].resources.limits.cpu 27 | value: "768m" 28 | - equal: 29 | path: spec.template.spec.containers[0].resources.requests.cpu 30 | value: "256m" 31 | - equal: 32 | path: spec.template.spec.containers[0].resources.limits.memory 33 | value: "1024Mi" 34 | - equal: 35 | path: spec.template.spec.containers[0].resources.requests.memory 36 | value: "1024Mi" 37 | - equal: 38 | path: metadata.labels.gitsha 39 | value: "ABC123" 40 | 41 | - it: "stac deployment includes configmap checksum annotation" 42 | set: 43 | stac.enabled: true 44 | raster.enabled: false 45 | vector.enabled: false 46 | multidim.enabled: false 47 | template: templates/services/stac/deployment.yaml 48 | asserts: 49 | - exists: 50 | path: spec.template.metadata.annotations["checksum/config"] 51 | - matchRegex: 52 | path: spec.template.metadata.annotations["checksum/config"] 53 | pattern: ^[a-f0-9]{64}$ 54 | 55 | - it: "stac configmap defaults" 56 | set: 57 | raster.enabled: false 58 | stac.enabled: true 59 | vector.enabled: false 60 | multidim.enabled: false 61 | template: templates/services/stac/configmap.yaml 62 | asserts: 63 | - isKind: 64 | of: ConfigMap 65 | - matchRegex: 66 | path: metadata.name 67 | pattern: ^RELEASE-NAME-stac-envvar-configmap$ 68 | - equal: 69 | path: data.WEB_CONCURRENCY 70 | value: "5" 71 | 72 | - it: "stac browser service" 73 | set: 74 | raster.enabled: false 75 | stac.enabled: true 76 | vector.enabled: false 77 | multidim.enabled: false 78 | browser.enabled: false 79 | stac.annotations: 80 | annotation1: hello 81 | annotation2: world 82 | gitSha: "ABC123" 83 | template: templates/services/stac/service.yaml 84 | asserts: 85 | - isKind: 86 | of: Service 87 | - matchRegex: 88 | path: metadata.name 89 | pattern: ^RELEASE-NAME-stac$ 90 | - matchRegex: 91 | path: metadata.labels.app 92 | pattern: ^RELEASE-NAME-stac$ 93 | - equal: 94 | path: metadata.annotations.annotation1 95 | value: hello 96 | - equal: 97 | path: metadata.annotations.annotation2 98 | value: world 99 | -------------------------------------------------------------------------------- /charts/eoapi/tests/multidim_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: multidim service tests 2 | templates: 3 | - templates/services/multidim/deployment.yaml 4 | - templates/services/multidim/configmap.yaml 5 | - templates/services/multidim/service.yaml 6 | - templates/services/multidim/hpa.yaml 7 | tests: 8 | - it: "multidim deployment defaults" 9 | set: 10 | raster.enabled: false 11 | stac.enabled: false 12 | vector.enabled: false 13 | multidim.enabled: true 14 | gitSha: "ABC123" 15 | template: templates/services/multidim/deployment.yaml 16 | asserts: 17 | - isKind: 18 | of: Deployment 19 | - matchRegex: 20 | path: metadata.name 21 | pattern: ^RELEASE-NAME-multidim$ 22 | - equal: 23 | path: spec.strategy.type 24 | value: "RollingUpdate" 25 | - equal: 26 | path: spec.template.spec.containers[0].resources.limits.cpu 27 | value: "768m" 28 | - equal: 29 | path: spec.template.spec.containers[0].resources.requests.cpu 30 | value: "256m" 31 | - equal: 32 | path: spec.template.spec.containers[0].resources.limits.memory 33 | value: "4096Mi" 34 | - equal: 35 | path: spec.template.spec.containers[0].resources.requests.memory 36 | value: "3072Mi" 37 | - equal: 38 | path: metadata.labels.gitsha 39 | value: "ABC123" 40 | 41 | - it: "multidim deployment includes configmap checksum annotation" 42 | set: 43 | multidim.enabled: true 44 | stac.enabled: false 45 | raster.enabled: false 46 | vector.enabled: false 47 | template: templates/services/multidim/deployment.yaml 48 | asserts: 49 | - exists: 50 | path: spec.template.metadata.annotations["checksum/config"] 51 | - matchRegex: 52 | path: spec.template.metadata.annotations["checksum/config"] 53 | pattern: ^[a-f0-9]{64}$ 54 | 55 | - it: "multidim configmap defaults" 56 | set: 57 | raster.enabled: false 58 | stac.enabled: false 59 | vector.enabled: false 60 | multidim.enabled: true 61 | template: templates/services/multidim/configmap.yaml 62 | asserts: 63 | - isKind: 64 | of: ConfigMap 65 | - matchRegex: 66 | path: metadata.name 67 | pattern: ^RELEASE-NAME-multidim-envvar-configmap$ 68 | - equal: 69 | path: data.GDAL_HTTP_MULTIPLEX 70 | value: "YES" 71 | 72 | - it: "multidim browser service" 73 | set: 74 | raster.enabled: false 75 | stac.enabled: false 76 | vector.enabled: false 77 | multidim.enabled: true 78 | browser.enabled: false 79 | multidim.annotations: 80 | annotation1: hello 81 | annotation2: world 82 | gitSha: "ABC123" 83 | template: templates/services/multidim/service.yaml 84 | asserts: 85 | - isKind: 86 | of: Service 87 | - matchRegex: 88 | path: metadata.name 89 | pattern: ^RELEASE-NAME-multidim$ 90 | - matchRegex: 91 | path: metadata.labels.app 92 | pattern: ^RELEASE-NAME-multidim$ 93 | - equal: 94 | path: metadata.annotations.annotation1 95 | value: hello 96 | - equal: 97 | path: metadata.annotations.annotation2 98 | value: world 99 | -------------------------------------------------------------------------------- /charts/eoapi/tests/vector_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: vector service tests 2 | templates: 3 | - templates/services/vector/deployment.yaml 4 | - templates/services/vector/configmap.yaml 5 | - templates/services/vector/service.yaml 6 | - templates/services/vector/hpa.yaml 7 | tests: 8 | - it: "vector deployment defaults" 9 | set: 10 | raster.enabled: false 11 | stac.enabled: false 12 | vector.enabled: true 13 | multidim.enabled: false 14 | 15 | gitSha: "ABC123" 16 | template: templates/services/vector/deployment.yaml 17 | asserts: 18 | - isKind: 19 | of: Deployment 20 | - matchRegex: 21 | path: metadata.name 22 | pattern: ^RELEASE-NAME-vector$ 23 | - equal: 24 | path: spec.strategy.type 25 | value: "RollingUpdate" 26 | - equal: 27 | path: spec.template.spec.containers[0].resources.limits.cpu 28 | value: "768m" 29 | - equal: 30 | path: spec.template.spec.containers[0].resources.requests.cpu 31 | value: "256m" 32 | - equal: 33 | path: spec.template.spec.containers[0].resources.limits.memory 34 | value: "1024Mi" 35 | - equal: 36 | path: spec.template.spec.containers[0].resources.requests.memory 37 | value: "256Mi" 38 | - equal: 39 | path: metadata.labels.gitsha 40 | value: "ABC123" 41 | 42 | - it: "vector deployment includes configmap checksum annotation" 43 | set: 44 | vector.enabled: true 45 | stac.enabled: false 46 | raster.enabled: false 47 | multidim.enabled: false 48 | template: templates/services/vector/deployment.yaml 49 | asserts: 50 | - exists: 51 | path: spec.template.metadata.annotations["checksum/config"] 52 | - matchRegex: 53 | path: spec.template.metadata.annotations["checksum/config"] 54 | pattern: ^[a-f0-9]{64}$ 55 | 56 | - it: "vector configmap defaults" 57 | set: 58 | raster.enabled: false 59 | stac.enabled: false 60 | vector.enabled: true 61 | multidim.enabled: false 62 | template: templates/services/vector/configmap.yaml 63 | asserts: 64 | - isKind: 65 | of: ConfigMap 66 | - matchRegex: 67 | path: metadata.name 68 | pattern: ^RELEASE-NAME-vector-envvar-configmap$ 69 | - equal: 70 | path: data.TIPG_CATALOG_TTL 71 | value: "300" 72 | 73 | - it: "vector browser service" 74 | set: 75 | raster.enabled: false 76 | stac.enabled: false 77 | vector.enabled: true 78 | multidim.enabled: false 79 | browser.enabled: false 80 | vector.annotations: 81 | annotation1: hello 82 | annotation2: world 83 | gitSha: "ABC123" 84 | template: templates/services/vector/service.yaml 85 | asserts: 86 | - isKind: 87 | of: Service 88 | - matchRegex: 89 | path: metadata.name 90 | pattern: ^RELEASE-NAME-vector$ 91 | - matchRegex: 92 | path: metadata.labels.app 93 | pattern: ^RELEASE-NAME-vector$ 94 | - equal: 95 | path: metadata.annotations.annotation1 96 | value: hello 97 | - equal: 98 | path: metadata.annotations.annotation2 99 | value: world 100 | -------------------------------------------------------------------------------- /charts/eoapi/tests/raster_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: raster service tests 2 | templates: 3 | - templates/services/raster/deployment.yaml 4 | - templates/services/raster/configmap.yaml 5 | - templates/services/raster/service.yaml 6 | - templates/services/raster/hpa.yaml 7 | tests: 8 | - it: "raster deployment defaults" 9 | set: 10 | raster.enabled: true 11 | stac.enabled: false 12 | vector.enabled: false 13 | multidim.enabled: false 14 | gitSha: "ABC123" 15 | template: templates/services/raster/deployment.yaml 16 | asserts: 17 | - isKind: 18 | of: Deployment 19 | - matchRegex: 20 | path: metadata.name 21 | pattern: ^RELEASE-NAME-raster$ 22 | - equal: 23 | path: spec.strategy.type 24 | value: "RollingUpdate" 25 | - equal: 26 | path: spec.template.spec.containers[0].resources.limits.cpu 27 | value: "768m" 28 | - equal: 29 | path: spec.template.spec.containers[0].resources.requests.cpu 30 | value: "256m" 31 | - equal: 32 | path: spec.template.spec.containers[0].resources.limits.memory 33 | value: "4096Mi" 34 | - equal: 35 | path: spec.template.spec.containers[0].resources.requests.memory 36 | value: "3072Mi" 37 | 38 | - it: "raster deployment includes configmap checksum annotation" 39 | set: 40 | raster.enabled: true 41 | stac.enabled: false 42 | vector.enabled: false 43 | multidim.enabled: false 44 | gitSha: "ABC123" 45 | template: templates/services/raster/deployment.yaml 46 | asserts: 47 | - exists: 48 | path: spec.template.metadata.annotations["checksum/config"] 49 | - matchRegex: 50 | path: spec.template.metadata.annotations["checksum/config"] 51 | pattern: ^[a-f0-9]{64}$ 52 | - equal: 53 | path: metadata.labels.gitsha 54 | value: "ABC123" 55 | 56 | - it: "raster configmap defaults" 57 | set: 58 | raster.enabled: true 59 | stac.enabled: false 60 | vector.enabled: false 61 | multidim.enabled: false 62 | template: templates/services/raster/configmap.yaml 63 | asserts: 64 | - isKind: 65 | of: ConfigMap 66 | - matchRegex: 67 | path: metadata.name 68 | pattern: ^RELEASE-NAME-raster-envvar-configmap$ 69 | - equal: 70 | path: data.GDAL_HTTP_MULTIPLEX 71 | value: "YES" 72 | 73 | - it: "raster browser service" 74 | set: 75 | raster.enabled: true 76 | stac.enabled: false 77 | vector.enabled: false 78 | multidim.enabled: false 79 | browser.enabled: false 80 | raster.annotations: 81 | annotation1: hello 82 | annotation2: world 83 | gitSha: "ABC123" 84 | template: templates/services/raster/service.yaml 85 | asserts: 86 | - isKind: 87 | of: Service 88 | - matchRegex: 89 | path: metadata.name 90 | pattern: ^RELEASE-NAME-raster$ 91 | - matchRegex: 92 | path: metadata.labels.app 93 | pattern: ^RELEASE-NAME-raster$ 94 | - equal: 95 | path: metadata.annotations.annotation1 96 | value: hello 97 | - equal: 98 | path: metadata.annotations.annotation2 99 | value: world 100 | -------------------------------------------------------------------------------- /charts/eoapi/templates/database/pgstacbootstrap/eoap-superuser-initdb.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.postgrescluster.enabled .Values.pgstacBootstrap.enabled }} 2 | --- 3 | # The eoapi database user runs normally pgstac migrate. 4 | # But before some initial grants need to be given with a super user. 5 | # https://stac-utils.github.io/pgstac/pypgstac/#option-2-create-user-with-initial-grants 6 | apiVersion: batch/v1 7 | kind: Job 8 | metadata: 9 | name: {{ .Release.Name }}-pgstac-superuser-init-db 10 | labels: 11 | app: {{ .Release.Name }}-pgstac-superuser-init-db 12 | annotations: 13 | helm.sh/hook: "post-install,post-upgrade" 14 | helm.sh/hook-weight: "-6" 15 | helm.sh/hook-delete-policy: "before-hook-creation" 16 | spec: 17 | template: 18 | metadata: 19 | labels: 20 | app: {{ .Release.Name }}-pgstac-superuser-init-db 21 | spec: 22 | restartPolicy: Never 23 | containers: 24 | - name: pgstac-eoapiuser-permissions 25 | image: {{ .Values.pgstacBootstrap.image.name }}:{{ .Values.pgstacBootstrap.image.tag }} 26 | command: 27 | - "/bin/sh" 28 | - "-c" 29 | args: 30 | - | 31 | # Exit immediately if a command exits with a non-zero status 32 | set -e 33 | 34 | # Wait for the database to be ready 35 | echo "Waiting for database to be ready..." 36 | pypgstac pgready 37 | 38 | # Run the initial setup with superuser 39 | PGUSER=postgres psql -f /opt/sql/initdb.sql 40 | 41 | resources: 42 | {{- toYaml .Values.pgstacBootstrap.settings.resources | nindent 12 }} 43 | volumeMounts: 44 | - mountPath: /opt/sql 45 | name: {{ .Release.Name }}-initdb-config 46 | env: 47 | - name: PGUSER 48 | valueFrom: 49 | secretKeyRef: 50 | name: {{ $.Values.postgrescluster.name | default $.Release.Name }}-pguser-postgres 51 | key: user 52 | - name: PGPORT 53 | valueFrom: 54 | secretKeyRef: 55 | name: {{ $.Values.postgrescluster.name | default $.Release.Name }}-pguser-postgres 56 | key: port 57 | - name: PGHOST 58 | valueFrom: 59 | secretKeyRef: 60 | name: {{ $.Values.postgrescluster.name | default $.Release.Name }}-pguser-postgres 61 | key: host 62 | - name: PGPASSWORD 63 | valueFrom: 64 | secretKeyRef: 65 | name: {{ $.Values.postgrescluster.name | default $.Release.Name }}-pguser-postgres 66 | key: password 67 | - name: PGDATABASE 68 | valueFrom: 69 | secretKeyRef: 70 | name: {{ $.Values.postgrescluster.name | default $.Release.Name }}-pguser-postgres 71 | key: dbname 72 | volumes: 73 | - name: {{ .Release.Name }}-initdb-config 74 | configMap: 75 | name: {{ .Release.Name }}-initdb 76 | {{- with .Values.pgstacBootstrap.settings.affinity }} 77 | affinity: 78 | {{- toYaml . | nindent 8 }} 79 | {{- end }} 80 | {{- with .Values.pgstacBootstrap.settings.tolerations }} 81 | tolerations: 82 | {{- toYaml . | nindent 8 }} 83 | {{- end }} 84 | backoffLimit: 5 85 | {{- end }} 86 | -------------------------------------------------------------------------------- /charts/eoapi/tests/postgres_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: postgresql tests 2 | templates: 3 | - templates/services/stac/deployment.yaml 4 | tests: 5 | - it: should use custom cluster name for secret references when specified 6 | set: 7 | postgresql: 8 | type: postgrescluster 9 | postgrescluster: 10 | enabled: true 11 | name: custom-cluster 12 | users: 13 | - name: postgres 14 | databases: ["postgres"] 15 | - name: my-user 16 | databases: ["my-db"] 17 | stac: 18 | enabled: true 19 | raster: 20 | enabled: false 21 | vector: 22 | enabled: false 23 | multidim: 24 | enabled: false 25 | template: templates/services/stac/deployment.yaml 26 | asserts: 27 | - isKind: 28 | of: Deployment 29 | - contains: 30 | path: spec.template.spec.containers[0].env 31 | content: 32 | name: PGUSER 33 | valueFrom: 34 | secretKeyRef: 35 | name: custom-cluster-pguser-my-user 36 | key: user 37 | 38 | - it: should fallback to release name when no custom cluster name is specified 39 | release: 40 | name: test-release 41 | set: 42 | postgresql: 43 | type: postgrescluster 44 | postgrescluster: 45 | enabled: true 46 | users: 47 | - name: postgres 48 | databases: ["postgres"] 49 | - name: my-user 50 | databases: ["my-db"] 51 | stac: 52 | enabled: true 53 | raster: 54 | enabled: false 55 | vector: 56 | enabled: false 57 | multidim: 58 | enabled: false 59 | template: templates/services/stac/deployment.yaml 60 | asserts: 61 | - isKind: 62 | of: Deployment 63 | - contains: 64 | path: spec.template.spec.containers[0].env 65 | content: 66 | name: PGUSER 67 | valueFrom: 68 | secretKeyRef: 69 | name: test-release-pguser-my-user 70 | key: user 71 | 72 | - it: should fail when external-plaintext is used with postgrescluster enabled 73 | set: 74 | stac: 75 | enabled: true 76 | postgresql: 77 | type: external-plaintext 78 | external: 79 | host: test-db 80 | credentials: 81 | username: test 82 | password: test 83 | postgrescluster: 84 | enabled: true 85 | template: templates/services/stac/deployment.yaml 86 | asserts: 87 | - failedTemplate: 88 | errorMessage: When postgresql.type is 'external-plaintext', postgrescluster.enabled must be set to false 89 | 90 | - it: should fail when external-secret is used with postgrescluster enabled 91 | set: 92 | stac: 93 | enabled: true 94 | postgresql: 95 | type: external-secret 96 | external: 97 | host: test-db 98 | existingSecret: 99 | name: my-secret 100 | keys: 101 | username: user 102 | password: pass 103 | postgrescluster: 104 | enabled: true 105 | template: templates/services/stac/deployment.yaml 106 | asserts: 107 | - failedTemplate: 108 | errorMessage: When postgresql.type is 'external-secret', postgrescluster.enabled must be set to false 109 | -------------------------------------------------------------------------------- /charts/eoapi/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: eoapi 3 | description: Create a full Earth Observation API with Metadata, Raster and Vector services 4 | # A chart can be either an 'application' or a 'library' chart. 5 | # 6 | # Application charts are a collection of templates that can be packaged into versioned archives 7 | # to be deployed. 8 | # 9 | # Library charts provide useful utilities or functions for the chart developer. They're included as 10 | # a dependency of application charts to inject those utilities and functions into the rendering 11 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 12 | type: application 13 | kubeVersion: ">=1.23.0-0" 14 | 15 | # Artifacthub metadata 16 | icon: https://eoapi.dev/img/eoAPI.png 17 | annotations: 18 | artifacthub.io/changes: | 19 | - Remove pathType and pathSuffix configurations 20 | - Add upgrade job for pre-0.7.0 migrations 21 | - Add separate browser ingress configuration 22 | - Support custom PostgreSQL cluster naming 23 | artifacthub.io/links: | 24 | - name: GitHub Repository 25 | url: https://github.com/developmentseed/eoapi-k8s 26 | - name: Documentation 27 | url: https://github.com/developmentseed/eoapi-k8s/tree/main/docs 28 | artifacthub.io/maintainers: | 29 | - name: DevelopmentSeed 30 | email: eoapi@developmentseed.org 31 | artifacthub.io/keywords: | 32 | - earth observation 33 | - geospatial 34 | - kubernetes 35 | - stac 36 | - raster 37 | - vector 38 | 39 | # This is the chart version. This version number should be incremented each time you make changes 40 | # to the chart and its templates, including the app version. 41 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 42 | version: "0.8.1" 43 | 44 | # This is the version number of the application being deployed. This version number should be 45 | # incremented each time you make changes to the application. Versions are not expected to 46 | # follow Semantic Versioning. They should reflect the version the application is using. 47 | # It is recommended to use it with quotes. 48 | # We use the stac-fastapi-pgstac version as the app version 49 | appVersion: "6.1.0" 50 | 51 | dependencies: 52 | - name: postgrescluster 53 | version: 5.7.4 54 | repository: "https://devseed.com/eoapi-k8s/" 55 | condition: postgrescluster.enabled 56 | - name: eoapi-notifier 57 | version: 0.0.9 58 | repository: "oci://ghcr.io/developmentseed/charts" 59 | condition: eoapi-notifier.enabled 60 | - name: knative-operator 61 | version: v1.20.0 62 | repository: https://knative.github.io/operator 63 | condition: knative.enabled 64 | - name: metrics-server 65 | version: 7.4.12 66 | repository: https://charts.bitnami.com/bitnami 67 | condition: monitoring.metricsServer.enabled 68 | - name: prometheus 69 | version: 27.50.1 70 | repository: https://prometheus-community.github.io/helm-charts 71 | condition: monitoring.prometheus.enabled 72 | - name: prometheus-adapter 73 | version: 5.2.0 74 | repository: https://prometheus-community.github.io/helm-charts 75 | condition: monitoring.prometheusAdapter.enabled 76 | - name: grafana 77 | version: 10.3.1 78 | repository: https://grafana.github.io/helm-charts 79 | condition: observability.grafana.enabled 80 | - name: stac-auth-proxy 81 | version: "0.1.1" 82 | repository: "oci://ghcr.io/developmentseed/stac-auth-proxy/charts" 83 | condition: stac-auth-proxy.enabled 84 | -------------------------------------------------------------------------------- /docs/images/eoapi-k8s-raw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eoAPI 69 | -------------------------------------------------------------------------------- /charts/eoapi/templates/networking/ingress-browser.yaml: -------------------------------------------------------------------------------- 1 | # We need a separate ingress because browser has the prefix /browser hardcoded in the code 2 | {{- if and .Values.browser.enabled .Values.ingress.enabled (or (not (hasKey .Values.browser "ingress")) .Values.browser.ingress.enabled) }} 3 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} 4 | apiVersion: networking.k8s.io/v1 5 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} 6 | apiVersion: networking.k8s.io/v1beta1 7 | {{- else }} 8 | apiVersion: extensions/v1beta1 9 | {{- end }} 10 | kind: Ingress 11 | metadata: 12 | name: {{ .Release.Name }}-ingress-browser 13 | labels: 14 | app: {{ .Release.Name }}-ingress-browser 15 | annotations: 16 | {{- if .Values.ingress.annotations }} 17 | {{ toYaml .Values.ingress.annotations | indent 4 }} 18 | {{- end }} 19 | {{- if eq .Values.ingress.className "nginx" }} 20 | nginx.ingress.kubernetes.io/rewrite-target: /browser/$2 21 | nginx.ingress.kubernetes.io/use-regex: "true" 22 | {{- end }} 23 | # Temporary annotations for Traefik until uvicorn support real prefix in ASGI: https://github.com/encode/uvicorn/discussions/2490 24 | {{- if eq .Values.ingress.className "traefik" }} 25 | traefik.ingress.kubernetes.io/router.entrypoints: web 26 | traefik.ingress.kubernetes.io/router.middlewares: {{ $.Release.Namespace }}-{{ $.Release.Name }}-strip-prefix-middleware@kubernetescrd 27 | {{- end }} 28 | spec: 29 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 30 | ingressClassName: {{ .Values.ingress.className }} 31 | {{- end }} 32 | rules: 33 | {{- if .Values.ingress.hosts }} 34 | {{- range .Values.ingress.hosts }} 35 | - host: {{ . }} 36 | http: 37 | paths: 38 | {{- if and $.Values.browser.enabled (or (not (hasKey $.Values.browser "ingress")) $.Values.browser.ingress.enabled) }} 39 | - pathType: {{ if eq $.Values.ingress.className "nginx" }}ImplementationSpecific{{ else }}Prefix{{ end }} 40 | path: "/browser{{ if eq $.Values.ingress.className "nginx" }}(/|$)(.*){{ end }}" 41 | backend: 42 | service: 43 | name: {{ .Release.Name }}-browser 44 | port: 45 | number: 8080 46 | {{- end }} 47 | {{- end }} 48 | {{- else }} 49 | - {{- if .Values.ingress.host }} 50 | host: {{ .Values.ingress.host }} 51 | {{- end }} 52 | http: 53 | paths: 54 | {{- if and .Values.browser.enabled (or (not (hasKey .Values.browser "ingress")) .Values.browser.ingress.enabled) }} 55 | - pathType: {{ if eq .Values.ingress.className "nginx" }}ImplementationSpecific{{ else }}Prefix{{ end }} 56 | path: "/browser{{ if eq .Values.ingress.className "nginx" }}(/|$)(.*){{ end }}" 57 | backend: 58 | service: 59 | name: {{ .Release.Name }}-browser 60 | port: 61 | number: 8080 62 | {{- end }} 63 | {{- end }} 64 | {{- if and .Values.ingress.tls.enabled (or .Values.ingress.hosts .Values.ingress.host) }} 65 | tls: 66 | - hosts: 67 | {{- if .Values.ingress.hosts }} 68 | {{- range .Values.ingress.hosts }} 69 | - {{ . }} 70 | {{- end }} 71 | {{- else if .Values.ingress.host }} 72 | - {{ .Values.ingress.host }} 73 | {{- end }} 74 | secretName: {{ .Values.ingress.tls.secretName }} 75 | {{- end }} 76 | {{- end }} 77 | -------------------------------------------------------------------------------- /charts/eoapi/templates/mock-oidc/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.mockOidcServer.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ include "eoapi.fullname" . }}-mock-oidc-server 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "eoapi.labels" . | nindent 4 }} 9 | app.kubernetes.io/component: mock-oidc-server 10 | spec: 11 | replicas: {{ .Values.mockOidcServer.replicaCount | default 1 }} 12 | selector: 13 | matchLabels: 14 | {{- include "eoapi.selectorLabels" . | nindent 6 }} 15 | app.kubernetes.io/component: mock-oidc-server 16 | template: 17 | metadata: 18 | labels: 19 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 20 | app.kubernetes.io/component: mock-oidc-server 21 | spec: 22 | {{- if .Values.mockOidcServer.imagePullSecrets }} 23 | imagePullSecrets: 24 | {{- toYaml .Values.mockOidcServer.imagePullSecrets | nindent 8 }} 25 | {{- end }} 26 | containers: 27 | - name: mock-oidc 28 | image: "{{ if .Values.mockOidcServer.image }}{{ .Values.mockOidcServer.image.repository | default "ghcr.io/alukach/mock-oidc-server" }}:{{ .Values.mockOidcServer.image.tag | default "latest" }}{{ else }}ghcr.io/alukach/mock-oidc-server:latest{{ end }}" 29 | imagePullPolicy: {{ if .Values.mockOidcServer.image }}{{ .Values.mockOidcServer.image.pullPolicy | default "IfNotPresent" }}{{ else }}IfNotPresent{{ end }} 30 | env: 31 | - name: MOCK_OIDC_PORT 32 | value: "{{ .Values.mockOidcServer.port | default 8888 }}" 33 | - name: MOCK_OIDC_CLIENT_ID 34 | value: "{{ .Values.mockOidcServer.clientId | default "test-client" }}" 35 | - name: MOCK_OIDC_CLIENT_SECRET 36 | value: "{{ .Values.mockOidcServer.clientSecret | default "test-secret" }}" 37 | {{- if .Values.mockOidcServer.extraEnv }} 38 | {{- toYaml .Values.mockOidcServer.extraEnv | nindent 8 }} 39 | {{- end }} 40 | ports: 41 | - name: http 42 | containerPort: {{ .Values.mockOidcServer.port | default 8888 }} 43 | protocol: TCP 44 | startupProbe: 45 | httpGet: 46 | path: /.well-known/openid-configuration 47 | port: http 48 | initialDelaySeconds: 10 49 | periodSeconds: 5 50 | failureThreshold: 12 51 | timeoutSeconds: 3 52 | livenessProbe: 53 | httpGet: 54 | path: /.well-known/openid-configuration 55 | port: http 56 | initialDelaySeconds: 30 57 | periodSeconds: 10 58 | failureThreshold: 3 59 | timeoutSeconds: 3 60 | readinessProbe: 61 | httpGet: 62 | path: /.well-known/openid-configuration 63 | port: http 64 | initialDelaySeconds: 5 65 | periodSeconds: 5 66 | failureThreshold: 3 67 | timeoutSeconds: 3 68 | {{- if .Values.mockOidcServer.resources }} 69 | resources: 70 | {{- toYaml .Values.mockOidcServer.resources | nindent 10 }} 71 | {{- end }} 72 | {{- if .Values.mockOidcServer.nodeSelector }} 73 | nodeSelector: 74 | {{- toYaml .Values.mockOidcServer.nodeSelector | nindent 8 }} 75 | {{- end }} 76 | {{- if .Values.mockOidcServer.affinity }} 77 | affinity: 78 | {{- toYaml .Values.mockOidcServer.affinity | nindent 8 }} 79 | {{- end }} 80 | {{- if .Values.mockOidcServer.tolerations }} 81 | tolerations: 82 | {{- toYaml .Values.mockOidcServer.tolerations | nindent 8 }} 83 | {{- end }} 84 | {{- end }} 85 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # eoAPI Scripts 2 | 3 | This directory contains the implementation scripts for the eoAPI CLI. 4 | 5 | ## Structure 6 | 7 | ``` 8 | scripts/ 9 | ├── lib/ 10 | │ ├── common.sh # Shared utilities (logging, validation) 11 | │ └── k8s.sh # Kubernetes helper functions 12 | ├── cluster.sh # Cluster management (start, stop, clean, status, inspect) 13 | ├── deployment.sh # Deployment operations (run, debug) 14 | ├── test.sh # Test suites (schema, lint, unit, integration) 15 | ├── ingest.sh # Data ingestion 16 | └── docs.sh # Documentation (generate, serve) 17 | ``` 18 | 19 | ## Usage 20 | 21 | All scripts are accessed through the main CLI: 22 | 23 | ```bash 24 | ./eoapi-cli [options] 25 | 26 | # Examples 27 | ./eoapi-cli cluster start 28 | ./eoapi-cli deployment run 29 | ./eoapi-cli test all 30 | ./eoapi-cli ingest collections.json items.json 31 | ./eoapi-cli docs serve 32 | ``` 33 | 34 | ## CLI Reference 35 | 36 | The eoAPI CLI provides a unified interface for all operations: 37 | 38 | ### Cluster Management 39 | ```bash 40 | # Start local k3s cluster 41 | ./eoapi-cli cluster start 42 | 43 | # Check cluster status 44 | ./eoapi-cli cluster status 45 | 46 | # Stop cluster (preserves data) 47 | ./eoapi-cli cluster stop 48 | 49 | # Clean up cluster and temporary files 50 | ./eoapi-cli cluster clean 51 | 52 | # Detailed cluster diagnostics 53 | ./eoapi-cli cluster inspect 54 | ``` 55 | 56 | ### Deployment Operations 57 | ```bash 58 | # Deploy eoAPI 59 | ./eoapi-cli deployment run 60 | 61 | # Debug deployment 62 | ./eoapi-cli deployment debug 63 | ``` 64 | 65 | ### Testing 66 | ```bash 67 | # Run all tests 68 | ./eoapi-cli test all 69 | 70 | # Run specific test suites 71 | ./eoapi-cli test schema # Validate Helm chart schema 72 | ./eoapi-cli test lint # Run Helm lint 73 | ./eoapi-cli test unit # Run Helm unit tests 74 | ./eoapi-cli test integration # Run integration tests 75 | ``` 76 | 77 | ### Data Ingestion 78 | ```bash 79 | # Ingest sample data 80 | ./eoapi-cli ingest 81 | ``` 82 | 83 | ### Documentation 84 | ```bash 85 | # Generate documentation 86 | ./eoapi-cli docs generate 87 | 88 | # Serve documentation locally 89 | ./eoapi-cli docs serve 90 | 91 | # Check documentation 92 | ./eoapi-cli docs check 93 | ``` 94 | 95 | ### Getting Help 96 | ```bash 97 | # Show main help 98 | ./eoapi-cli --help 99 | 100 | # Show command-specific help 101 | ./eoapi-cli cluster --help 102 | ./eoapi-cli deployment --help 103 | ./eoapi-cli test --help 104 | ``` 105 | 106 | ## Integration testing 107 | 108 | ### With k3d (recommended) 109 | ```bash 110 | # Complete workflow with k3d-managed cluster 111 | ./eoapi-cli cluster start # Creates k3d cluster 112 | ./eoapi-cli deployment run # Deploy eoAPI 113 | ./eoapi-cli test integration # Run integration tests 114 | ./eoapi-cli cluster clean # Cleanup 115 | ``` 116 | 117 | ### With existing k3s/k8s cluster 118 | ```bash 119 | # Ensure kubectl is configured for your cluster 120 | ./eoapi-cli deployment run # Deploy eoAPI 121 | ./eoapi-cli test integration # Run tests 122 | ``` 123 | 124 | Test options: 125 | - `test all` - Run all test suites 126 | - `test integration --pytest-args="-v"` - Pass pytest arguments 127 | 128 | ## Environment variables 129 | 130 | - `NAMESPACE` - Kubernetes namespace (default: eoapi) 131 | - `RELEASE_NAME` - Helm release name (default: eoapi) 132 | - `DEBUG_MODE` - Enable debug output (set to true) 133 | - `CLUSTER_NAME` - K3s cluster name (default: eoapi-local) 134 | 135 | The scripts auto-detect CI environments through common environment variables (CI, GITHUB_ACTIONS, etc). 136 | -------------------------------------------------------------------------------- /tests/integration/test_stac_auth.py: -------------------------------------------------------------------------------- 1 | """Test STAC API with auth proxy authentication.""" 2 | 3 | import os 4 | import time 5 | 6 | import httpx 7 | import pytest 8 | 9 | timeout = httpx.Timeout(15.0, connect=60.0) 10 | client = httpx.Client( 11 | timeout=timeout, 12 | verify=not bool(os.getenv("IGNORE_SSL_VERIFICATION", False)), 13 | ) 14 | 15 | 16 | @pytest.fixture 17 | def valid_token(auth_token: str) -> str: 18 | """Get valid JWT token for auth testing.""" 19 | return auth_token 20 | 21 | 22 | def test_stac_auth_without_token(stac_endpoint: str) -> None: 23 | """Test write operation without token - should be rejected.""" 24 | resp = client.post( 25 | f"{stac_endpoint}/collections/noaa-emergency-response/items", 26 | headers={"Content-Type": "application/json"}, 27 | json={ 28 | "id": f"test-no-token-{int(time.time() * 1000)}", 29 | "type": "Feature", 30 | "stac_version": "1.0.0", 31 | "properties": {"datetime": "2024-01-01T00:00:00Z"}, 32 | "geometry": {"type": "Point", "coordinates": [0, 0]}, 33 | "links": [], 34 | "assets": {}, 35 | "collection": "noaa-emergency-response", 36 | "bbox": [-0.1, -0.1, 0.1, 0.1], 37 | }, 38 | ) 39 | 40 | if resp.status_code in [200, 201]: 41 | # Auth proxy should reject requests without tokens 42 | assert resp.status_code in [401, 403], ( 43 | f"Expected auth error, got {resp.status_code}: {resp.text[:100]}" 44 | ) 45 | 46 | 47 | def test_stac_auth_with_invalid_token(stac_endpoint: str) -> None: 48 | """Test write operation with invalid token - should be rejected.""" 49 | resp = client.post( 50 | f"{stac_endpoint}/collections/noaa-emergency-response/items", 51 | headers={ 52 | "Authorization": "Bearer invalid-token", 53 | "Content-Type": "application/json", 54 | }, 55 | json={ 56 | "id": f"test-invalid-token-{int(time.time() * 1000)}", 57 | "type": "Feature", 58 | "stac_version": "1.0.0", 59 | "properties": {"datetime": "2024-01-01T00:00:00Z"}, 60 | "geometry": {"type": "Point", "coordinates": [0, 0]}, 61 | "links": [], 62 | "assets": {}, 63 | "collection": "noaa-emergency-response", 64 | "bbox": [-0.1, -0.1, 0.1, 0.1], 65 | }, 66 | ) 67 | 68 | assert resp.status_code in [401, 403], ( 69 | f"Expected auth error with invalid token, got {resp.status_code}: {resp.text[:100]}" 70 | ) 71 | 72 | 73 | def test_stac_auth_with_valid_token( 74 | stac_endpoint: str, valid_token: str 75 | ) -> None: 76 | """Test write operation with valid token - tests actual auth proxy behavior.""" 77 | resp = client.post( 78 | f"{stac_endpoint}/collections/noaa-emergency-response/items", 79 | headers={ 80 | "Authorization": valid_token, 81 | "Content-Type": "application/json", 82 | }, 83 | json={ 84 | "id": f"test-valid-token-{int(time.time() * 1000)}", 85 | "type": "Feature", 86 | "stac_version": "1.0.0", 87 | "properties": {"datetime": "2024-01-01T00:00:00Z"}, 88 | "geometry": {"type": "Point", "coordinates": [0, 0]}, 89 | "links": [], 90 | "assets": {}, 91 | "collection": "noaa-emergency-response", 92 | "bbox": [-0.1, -0.1, 0.1, 0.1], 93 | }, 94 | ) 95 | 96 | # With valid token from mock OIDC server, request should succeed 97 | assert resp.status_code in [200, 201], ( 98 | f"Expected success with valid token, got {resp.status_code}: {resp.text[:100]}" 99 | ) 100 | 101 | 102 | def test_stac_read_operations_work(stac_endpoint: str) -> None: 103 | """Test that read operations work without auth.""" 104 | resp = client.get(stac_endpoint) 105 | assert resp.status_code == 200 106 | 107 | resp = client.get(f"{stac_endpoint}/collections") 108 | assert resp.status_code == 200 109 | -------------------------------------------------------------------------------- /scripts/ingest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # eoAPI Data Ingestion Script 4 | 5 | SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" 6 | source "$SCRIPT_DIR/lib/common.sh" 7 | 8 | DEFAULT_COLLECTIONS_FILE="./collections.json" 9 | DEFAULT_ITEMS_FILE="./items.json" 10 | 11 | if [ "$#" -eq 2 ]; then 12 | EOAPI_COLLECTIONS_FILE="$1" 13 | EOAPI_ITEMS_FILE="$2" 14 | else 15 | EOAPI_COLLECTIONS_FILE="$DEFAULT_COLLECTIONS_FILE" 16 | EOAPI_ITEMS_FILE="$DEFAULT_ITEMS_FILE" 17 | log_info "No specific files provided. Using defaults:" 18 | log_info " Collections file: $EOAPI_COLLECTIONS_FILE" 19 | log_info " Items file: $EOAPI_ITEMS_FILE" 20 | fi 21 | 22 | # Run pre-flight checks 23 | if ! preflight_ingest "$(detect_namespace)" "$EOAPI_COLLECTIONS_FILE" "$EOAPI_ITEMS_FILE"; then 24 | exit 1 25 | fi 26 | 27 | # Detect namespace and raster pod 28 | FOUND_NAMESPACE=$(detect_namespace) 29 | log_info "Using namespace: $FOUND_NAMESPACE" 30 | 31 | # Find raster pod using multiple patterns 32 | EOAPI_POD_RASTER="" 33 | PATTERNS=( 34 | "app=raster-eoapi" 35 | "app.kubernetes.io/name=raster" 36 | "app.kubernetes.io/component=raster" 37 | ) 38 | 39 | for pattern in "${PATTERNS[@]}"; do 40 | EOAPI_POD_RASTER=$(kubectl get pods -n "$FOUND_NAMESPACE" -l "$pattern" -o jsonpath="{.items[0].metadata.name}" 2>/dev/null || echo "") 41 | if [ -n "$EOAPI_POD_RASTER" ]; then 42 | log_info "Found raster pod: $EOAPI_POD_RASTER (pattern: $pattern)" 43 | break 44 | fi 45 | done 46 | 47 | # Check if the pod was found 48 | if [ -z "$EOAPI_POD_RASTER" ]; then 49 | log_error "Could not find raster pod in namespace: $FOUND_NAMESPACE" 50 | log_error "Available pods:" 51 | kubectl get pods -n "$FOUND_NAMESPACE" -o name 2>/dev/null || true 52 | exit 1 53 | fi 54 | 55 | # Validate pod is ready 56 | log_info "Validating pod readiness..." 57 | if ! kubectl wait --for=condition=Ready pod "$EOAPI_POD_RASTER" -n "$FOUND_NAMESPACE" --timeout=30s; then 58 | log_error "Pod $EOAPI_POD_RASTER is not ready" 59 | kubectl describe pod "$EOAPI_POD_RASTER" -n "$FOUND_NAMESPACE" 60 | exit 1 61 | fi 62 | 63 | # Check if pypgstac is already available (avoid unnecessary installations) 64 | log_info "Checking for pypgstac in pod..." 65 | if kubectl exec -n "$FOUND_NAMESPACE" "$EOAPI_POD_RASTER" -- python3 -c "import pypgstac" >/dev/null 2>&1; then 66 | log_info "pypgstac already available" 67 | else 68 | log_info "Installing pypgstac in pod $EOAPI_POD_RASTER..." 69 | if ! kubectl exec -n "$FOUND_NAMESPACE" "$EOAPI_POD_RASTER" -- bash -c 'pip install pypgstac[psycopg]'; then 70 | log_error "Failed to install pypgstac" 71 | exit 1 72 | fi 73 | fi 74 | 75 | # Copy files to pod 76 | log_info "Copying files to pod..." 77 | log_info " Collections: $EOAPI_COLLECTIONS_FILE" 78 | log_info " Items: $EOAPI_ITEMS_FILE" 79 | 80 | if ! kubectl cp "$EOAPI_COLLECTIONS_FILE" "$FOUND_NAMESPACE/$EOAPI_POD_RASTER":/tmp/collections.json; then 81 | log_error "Failed to copy collections file" 82 | exit 1 83 | fi 84 | 85 | if ! kubectl cp "$EOAPI_ITEMS_FILE" "$FOUND_NAMESPACE/$EOAPI_POD_RASTER":/tmp/items.json; then 86 | log_error "Failed to copy items file" 87 | exit 1 88 | fi 89 | 90 | # Load collections and items 91 | log_info "Loading collections..." 92 | if ! kubectl exec -n "$FOUND_NAMESPACE" "$EOAPI_POD_RASTER" -- bash -c "pypgstac load collections /tmp/collections.json --dsn \"\$PGADMIN_URI\" --method insert_ignore"; then 93 | log_error "Failed to load collections" 94 | exit 1 95 | fi 96 | log_info "Collections loaded successfully" 97 | 98 | log_info "Loading items..." 99 | if ! kubectl exec -n "$FOUND_NAMESPACE" "$EOAPI_POD_RASTER" -- bash -c "pypgstac load items /tmp/items.json --dsn \"\$PGADMIN_URI\" --method insert_ignore"; then 100 | log_error "Failed to load items" 101 | exit 1 102 | fi 103 | log_info "Items loaded successfully" 104 | 105 | # Clean temporary files 106 | log_info "Cleaning temporary files..." 107 | kubectl exec -n "$FOUND_NAMESPACE" "$EOAPI_POD_RASTER" -- bash -c 'rm -f /tmp/collections.json /tmp/items.json' || log_warn "Failed to clean temporary files" 108 | 109 | log_info "✅ Ingestion completed successfully" 110 | -------------------------------------------------------------------------------- /charts/eoapi/profiles/README.md: -------------------------------------------------------------------------------- 1 | # eoAPI Helm Chart Profiles 2 | 3 | This directory contains pre-configured values profiles for common eoAPI deployment scenarios. These profiles simplify deployment by providing sensible defaults for different use cases. 4 | 5 | ## Overview 6 | 7 | Profiles are pre-configured values files that override the default `values.yaml` settings. They help you quickly deploy eoAPI for different scenarios without manually configuring dozens of parameters. 8 | 9 | ## Available Profiles 10 | 11 | ### Core Profile (`core.yaml`) 12 | **Use Case:** Minimal production deployment with stable services only. 13 | 14 | **Includes:** 15 | - PostgreSQL with PgSTAC 16 | - STAC API 17 | - Raster service (TiTiler) 18 | - Vector service (TiPG) 19 | - Documentation server 20 | 21 | **Excludes:** 22 | - Experimental features 23 | - Development tools 24 | - Monitoring stack 25 | - STAC Browser UI 26 | - Autoscaling 27 | 28 | **Resources:** Production-optimized with higher resource allocations. 29 | 30 | ### Production Profile (`production.yaml`) 31 | **Use Case:** Full production deployment with autoscaling and observability. 32 | 33 | **Includes:** 34 | - All core services 35 | - High availability PostgreSQL (2 replicas) 36 | - Autoscaling for all API services 37 | - Complete monitoring stack (Prometheus) 38 | - Grafana dashboards for observability 39 | - STAC Browser UI 40 | - Custom metrics for request-rate scaling 41 | 42 | **Configuration:** 43 | - Autoscaling enabled (CPU and request-rate based) 44 | - Persistent storage for metrics (30 days retention) 45 | - Production-optimized resource allocations 46 | - TLS enabled by default 47 | 48 | **Resources:** High resource allocations optimized for production workloads. 49 | 50 | ### Experimental Profile (`experimental.yaml`) 51 | **Use Case:** Development, testing, and evaluation of all eoAPI features. 52 | 53 | **Includes:** 54 | - All core services 55 | - Multidimensional service 56 | - STAC Browser UI 57 | - Notification system (eoapi-notifier) 58 | - Knative integration for CloudEvents 59 | - Complete monitoring stack (Prometheus, Grafana) 60 | - Sample data loading 61 | - Debug modes enabled 62 | 63 | **Resources:** Balanced for development environments. 64 | 65 | ### Local Profiles 66 | 67 | #### k3s Profile (`local/k3s.yaml`) 68 | **Use Case:** Local development on k3s clusters. 69 | 70 | **Requirements:** Must be used together with `experimental.yaml` 71 | 72 | **Overrides:** 73 | - Traefik ingress configuration 74 | - Reduced PostgreSQL resources (1Gi storage, minimal CPU/memory) 75 | - Disabled metrics-server (k3s provides built-in) 76 | 77 | #### Minikube Profile (`local/minikube.yaml`) 78 | **Use Case:** Local development on Minikube. 79 | 80 | **Requirements:** Must be used together with `experimental.yaml` 81 | 82 | **Overrides:** 83 | - Nginx ingress configuration with regex support 84 | - Reduced PostgreSQL resources (1Gi storage, minimal CPU/memory) 85 | - Enabled metrics-server 86 | 87 | ## Usage 88 | 89 | ### Basic Usage 90 | 91 | Deploy with a single profile: 92 | ```bash 93 | # Minimal production deployment 94 | helm install eoapi ./charts/eoapi -f profiles/core.yaml 95 | 96 | # Full production with autoscaling and observability 97 | helm install eoapi ./charts/eoapi -f profiles/production.yaml 98 | 99 | # Development deployment with all experimental features 100 | helm install eoapi ./charts/eoapi -f profiles/experimental.yaml 101 | ``` 102 | 103 | ### Layered Profiles 104 | 105 | Combine profiles for specific environments: 106 | ```bash 107 | # k3s local development 108 | helm install eoapi ./charts/eoapi \ 109 | -f profiles/experimental.yaml \ 110 | -f profiles/local/k3s.yaml 111 | 112 | # Minikube local development 113 | helm install eoapi ./charts/eoapi \ 114 | -f profiles/experimental.yaml \ 115 | -f profiles/local/minikube.yaml 116 | ``` 117 | 118 | ### Custom Overrides 119 | 120 | Add your own overrides on top of profiles: 121 | ```bash 122 | # Use core profile with custom domain 123 | helm install eoapi ./charts/eoapi \ 124 | -f profiles/core.yaml \ 125 | --set ingress.host=api.example.com 126 | 127 | # Use experimental profile with external database 128 | helm install eoapi ./charts/eoapi \ 129 | -f profiles/experimental.yaml \ 130 | -f my-custom-values.yaml 131 | ``` 132 | -------------------------------------------------------------------------------- /charts/eoapi/templates/database/pgstacbootstrap/configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.pgstacBootstrap.enabled }} 2 | --- 3 | # These ConfigMaps provide the necessary data and scripts for the pgstacbootstrap job. 4 | # They use Helm hooks with a weight of "-6" (lower than the job's "-5") to ensure 5 | # they are created before the job that depends on them. 6 | apiVersion: v1 7 | kind: ConfigMap 8 | metadata: 9 | name: {{ $.Release.Name }}-pgstac-settings-config 10 | annotations: 11 | helm.sh/hook: "post-install,post-upgrade" 12 | helm.sh/hook-weight: "-7" 13 | helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" 14 | data: 15 | pgstac-settings.sql: | 16 | {{- tpl ($.Files.Get "initdb-data/settings/pgstac-settings.sql.tpl") $ | nindent 4 }} 17 | {{- if (index .Values "eoapi-notifier").enabled }} 18 | {{ $.Files.Get "initdb-data/settings/pgstac-notification-triggers.sql" | nindent 4 }} 19 | {{- end }} 20 | --- 21 | {{- if .Values.pgstacBootstrap.settings.loadSamples }} 22 | apiVersion: v1 23 | kind: ConfigMap 24 | metadata: 25 | name: {{ $.Release.Name }}-initdb-sql-config 26 | annotations: 27 | helm.sh/hook: "post-install,post-upgrade" 28 | helm.sh/hook-weight: "-7" 29 | helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" 30 | data: 31 | initdb.sql: | 32 | {{- range $path, $bytes := $.Files.Glob "initdb-data/samples/*.sql" -}} 33 | {{ $.Files.Get $path | nindent 4 }} 34 | {{- end }} 35 | --- 36 | apiVersion: v1 37 | kind: ConfigMap 38 | metadata: 39 | name: {{ $.Release.Name }}-initdb-json-config 40 | annotations: 41 | helm.sh/hook: "post-install,post-upgrade" 42 | helm.sh/hook-weight: "-7" 43 | helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" 44 | data: 45 | {{- range $path, $bytes := $.Files.Glob "initdb-data/samples/*.json" -}} 46 | {{- base $path | nindent 2 -}}: | {{- $.Files.Get $path | nindent 4 -}} 47 | {{- end }} 48 | {{- end }} 49 | --- 50 | {{- if .Values.pgstacBootstrap.settings.queryables }} 51 | {{- $hasFileBasedQueryables := false }} 52 | {{- range $config := .Values.pgstacBootstrap.settings.queryables }} 53 | {{- if and $config.file (typeIs "string" $config.file) }} 54 | {{- $hasFileBasedQueryables = true }} 55 | {{- end }} 56 | {{- end }} 57 | {{- if $hasFileBasedQueryables }} 58 | apiVersion: v1 59 | kind: ConfigMap 60 | metadata: 61 | name: {{ $.Release.Name }}-pgstac-queryables-config 62 | annotations: 63 | helm.sh/hook: "post-install,post-upgrade" 64 | helm.sh/hook-weight: "-7" 65 | helm.sh/hook-delete-policy: "before-hook-creation,hook-succeeded" 66 | data: 67 | {{- range $config := .Values.pgstacBootstrap.settings.queryables }} 68 | {{- if and $config.file (typeIs "string" $config.file) }} 69 | {{ $config.name }}: | 70 | {{- $.Files.Get $config.file | nindent 4 }} 71 | {{- end }} 72 | {{- end }} 73 | {{- end }} 74 | {{- end }} 75 | {{- end }} 76 | --- 77 | {{- if .Values.postgrescluster.enabled }} 78 | apiVersion: v1 79 | kind: ConfigMap 80 | metadata: 81 | name: {{ .Release.Name }}-initdb 82 | data: 83 | initdb.sql: | 84 | \c {{ .Values.pgstacBootstrap.settings.database }} 85 | CREATE EXTENSION IF NOT EXISTS postgis; 86 | CREATE EXTENSION IF NOT EXISTS btree_gist; 87 | CREATE EXTENSION IF NOT EXISTS unaccent; 88 | CREATE ROLE pgstac_admin; 89 | CREATE ROLE pgstac_read; 90 | CREATE ROLE pgstac_ingest; 91 | ALTER DATABASE {{ .Values.pgstacBootstrap.settings.database }} OWNER TO {{ .Values.pgstacBootstrap.settings.user }}; 92 | ALTER USER {{ .Values.pgstacBootstrap.settings.user }} SET search_path TO pgstac, public; 93 | ALTER ROLE {{ .Values.pgstacBootstrap.settings.user }} WITH CREATEROLE; 94 | ALTER DATABASE {{ .Values.pgstacBootstrap.settings.database }} set search_path to pgstac, public; 95 | GRANT CONNECT ON DATABASE {{ .Values.pgstacBootstrap.settings.database }} TO {{ .Values.pgstacBootstrap.settings.user }}; 96 | GRANT ALL PRIVILEGES ON TABLES TO {{ .Values.pgstacBootstrap.settings.user }}; 97 | GRANT ALL PRIVILEGES ON SEQUENCES TO {{ .Values.pgstacBootstrap.settings.user }}; 98 | GRANT pgstac_read TO {{ .Values.pgstacBootstrap.settings.user }} WITH ADMIN OPTION; 99 | GRANT pgstac_ingest TO {{ .Values.pgstacBootstrap.settings.user }} WITH ADMIN OPTION; 100 | GRANT pgstac_admin TO {{ .Values.pgstacBootstrap.settings.user }} WITH ADMIN OPTION; 101 | {{- end }} 102 | -------------------------------------------------------------------------------- /charts/eoapi/templates/_helpers/services.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Helper function for mounting service secrets 3 | Only extract truly common elements that are mechanical and don't need customization 4 | */}} 5 | {{- define "eoapi.mountServiceSecrets" -}} 6 | {{- $service := .service -}} 7 | {{- $root := .root -}} 8 | {{- if index $root.Values $service "settings" "envSecrets" }} 9 | {{- range $secret := index $root.Values $service "settings" "envSecrets" }} 10 | - secretRef: 11 | name: {{ $secret }} 12 | {{- end }} 13 | {{- end }} 14 | {{- end -}} 15 | 16 | {{/* 17 | Helper function for common environment variables 18 | */}} 19 | {{- define "eoapi.commonEnvVars" -}} 20 | {{- $service := .service -}} 21 | {{- $root := .root -}} 22 | - name: SERVICE_NAME 23 | value: {{ $service | quote }} 24 | - name: RELEASE_NAME 25 | value: {{ $root.Release.Name | quote }} 26 | - name: GIT_SHA 27 | value: {{ $root.Values.gitSha | quote }} 28 | {{- end -}} 29 | 30 | {{/* 31 | Helper function for common init containers to wait for pgstac jobs 32 | */}} 33 | {{- define "eoapi.pgstacInitContainers" -}} 34 | {{- if .Values.pgstacBootstrap.enabled }} 35 | initContainers: 36 | - name: wait-for-pgstac-jobs 37 | image: alpine/k8s:1.28.0 38 | env: 39 | {{- include "eoapi.commonEnvVars" (dict "service" "init" "root" .) | nindent 2 }} 40 | resources: 41 | requests: 42 | cpu: "50m" 43 | memory: "64Mi" 44 | limits: 45 | cpu: "100m" 46 | memory: "128Mi" 47 | command: 48 | - /bin/sh 49 | - -c 50 | - | 51 | set -eu 52 | 53 | # Configurable parameters with values.yaml support and environment variable fallback 54 | SLEEP_INTERVAL="${PGSTAC_WAIT_SLEEP_INTERVAL:-{{ .Values.pgstacBootstrap.settings.waitConfig.sleepInterval | default 5 }}}" 55 | TIMEOUT_SECONDS="${PGSTAC_WAIT_TIMEOUT:-{{ .Values.pgstacBootstrap.settings.waitConfig.timeout | default 900 }}}" 56 | 57 | wait_for_job_by_label () { 58 | label_selector="$1" 59 | job_description="$2" 60 | echo "Waiting for job with label $label_selector to complete (timeout: ${TIMEOUT_SECONDS}s, interval: ${SLEEP_INTERVAL}s)..." 61 | deadline=$(( $(date +%s) + TIMEOUT_SECONDS )) 62 | 63 | while :; do 64 | # Check if deadline exceeded 65 | [ $(date +%s) -ge $deadline ] && { echo "Timeout waiting for $job_description job"; exit 1; } 66 | 67 | # Get jobs matching the label 68 | jobs=$(kubectl get job -l "$label_selector" -o name 2>/dev/null || true) 69 | 70 | if [ -z "$jobs" ]; then 71 | echo "No $job_description jobs found yet, waiting..." 72 | sleep 5 73 | continue 74 | fi 75 | 76 | # Check each job's status 77 | all_complete=true 78 | any_failed=false 79 | 80 | for job in $jobs; do 81 | # Get completion and failure status 82 | complete_status=$(kubectl get "$job" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' 2>/dev/null || echo "Unknown") 83 | failed_status=$(kubectl get "$job" -o jsonpath='{.status.conditions[?(@.type=="Failed")].status}' 2>/dev/null || echo "False") 84 | 85 | job_name=$(echo "$job" | cut -d'/' -f2) 86 | 87 | if [ "$failed_status" = "True" ]; then 88 | echo "ERROR: $job_description job $job_name failed!" 89 | echo "Job details:" 90 | kubectl describe "$job" || true 91 | echo "Job logs:" 92 | kubectl logs -l "job-name=$job_name" --tail=50 || true 93 | any_failed=true 94 | elif [ "$complete_status" != "True" ]; then 95 | echo "$job_description job $job_name not yet complete (Complete: $complete_status, Failed: $failed_status)" 96 | all_complete=false 97 | else 98 | echo "$job_description job $job_name completed successfully" 99 | fi 100 | done 101 | 102 | # Exit with error if any job failed 103 | [ "$any_failed" = true ] && exit 1 104 | 105 | # Exit successfully if all jobs completed 106 | [ "$all_complete" = true ] && return 0 107 | 108 | sleep $SLEEP_INTERVAL 109 | done 110 | } 111 | 112 | wait_for_job_by_label "app={{ .Release.Name }}-pgstac-migrate" "pgstac-migrate" 113 | {{- if .Values.pgstacBootstrap.settings.loadSamples }} 114 | wait_for_job_by_label "app={{ .Release.Name }}-pgstac-load-samples" "pgstac-load-samples" 115 | {{- end }} 116 | {{- end }} 117 | {{- end -}} 118 | -------------------------------------------------------------------------------- /charts/eoapi/profiles/core.yaml: -------------------------------------------------------------------------------- 1 | # eoAPI Core Profile 2 | # Stable, production-ready services only 3 | # Includes: PostgreSQL, PgSTAC, STAC API, Raster (TiTiler), Vector (TiPG) 4 | # 5 | # Usage: 6 | # helm install eoapi ./charts/eoapi -f profiles/core.yaml 7 | # helm upgrade eoapi ./charts/eoapi -f profiles/core.yaml 8 | 9 | # Core services only 10 | testing: false 11 | 12 | ###################### 13 | # DATABASE 14 | ###################### 15 | # PostgreSQL cluster configuration for production 16 | postgresql: 17 | type: "postgrescluster" 18 | 19 | postgrescluster: 20 | enabled: true 21 | postgresVersion: 16 22 | postGISVersion: "3.4" 23 | pgBouncerReplicas: 1 24 | monitoring: false 25 | instances: 26 | - name: eoapi 27 | replicas: 1 28 | dataVolumeClaimSpec: 29 | accessModes: 30 | - "ReadWriteOnce" 31 | resources: 32 | requests: 33 | storage: "10Gi" 34 | resources: {} 35 | users: 36 | - name: postgres 37 | databases: 38 | - eoapi 39 | - postgres 40 | options: "SUPERUSER" 41 | - name: eoapi 42 | databases: 43 | - eoapi 44 | - postgres 45 | options: "CREATEDB CREATEROLE" 46 | password: 47 | type: AlphaNumeric 48 | 49 | ###################### 50 | # PGSTAC BOOTSTRAP 51 | ###################### 52 | pgstacBootstrap: 53 | enabled: true 54 | settings: 55 | # Disable sample data for production 56 | loadSamples: false 57 | 58 | # Production-optimized PgSTAC settings 59 | pgstacSettings: 60 | queue_timeout: "10 minutes" 61 | use_queue: "false" 62 | update_collection_extent: "true" 63 | context: "auto" 64 | context_estimated_count: "100000" 65 | context_estimated_cost: "100000" 66 | context_stats_ttl: "1 day" 67 | 68 | resources: {} 69 | 70 | ###################### 71 | # CORE API SERVICES 72 | ###################### 73 | # Only stable, production-ready services 74 | stac: 75 | enabled: true 76 | ingress: 77 | enabled: true 78 | path: "/stac" 79 | autoscaling: 80 | enabled: false 81 | settings: 82 | resources: {} 83 | envVars: 84 | HOST: "0.0.0.0" 85 | PORT: "8080" 86 | WEB_CONCURRENCY: "4" 87 | # Production settings 88 | STAC_FASTAPI_DEBUG: "False" 89 | STAC_FASTAPI_CORS_ORIGINS: '["*"]' 90 | 91 | raster: 92 | enabled: true 93 | ingress: 94 | enabled: true 95 | path: "/raster" 96 | autoscaling: 97 | enabled: false 98 | settings: 99 | resources: {} 100 | envVars: 101 | # GDAL performance settings 102 | GDAL_CACHEMAX: "200" 103 | GDAL_DISABLE_READDIR_ON_OPEN: "EMPTY_DIR" 104 | GDAL_INGESTED_BYTES_AT_OPEN: "32768" 105 | GDAL_HTTP_MERGE_CONSECUTIVE_RANGES: "YES" 106 | GDAL_HTTP_MULTIPLEX: "YES" 107 | GDAL_HTTP_VERSION: "2" 108 | PYTHONWARNINGS: "ignore" 109 | VSI_CACHE: "TRUE" 110 | VSI_CACHE_SIZE: "5000000" 111 | # Uvicorn settings 112 | HOST: "0.0.0.0" 113 | PORT: "8080" 114 | WEB_CONCURRENCY: "4" 115 | # Production settings 116 | TITILER_DEBUG: "False" 117 | 118 | vector: 119 | enabled: true 120 | ingress: 121 | enabled: true 122 | path: "/vector" 123 | autoscaling: 124 | enabled: false 125 | settings: 126 | resources: {} 127 | envVars: 128 | TIPG_CATALOG_TTL: "300" 129 | TIPG_DEBUG: "False" 130 | HOST: "0.0.0.0" 131 | PORT: "8080" 132 | WEB_CONCURRENCY: "4" 133 | 134 | ###################### 135 | # DISABLED SERVICES 136 | ###################### 137 | # Experimental and optional services disabled 138 | multidim: 139 | enabled: false 140 | 141 | browser: 142 | enabled: false 143 | 144 | docServer: 145 | enabled: true 146 | 147 | eoapi-notifier: 148 | enabled: false 149 | 150 | knative: 151 | enabled: false 152 | 153 | monitoring: 154 | metricsServer: 155 | enabled: false 156 | prometheus: 157 | enabled: false 158 | prometheusAdapter: 159 | enabled: false 160 | 161 | observability: 162 | grafana: 163 | enabled: false 164 | 165 | ###################### 166 | # INGRESS 167 | ###################### 168 | ingress: 169 | enabled: true 170 | className: "nginx" 171 | pathType: "Prefix" 172 | host: "" 173 | tls: 174 | enabled: false 175 | secretName: eoapi-tls 176 | 177 | ###################### 178 | # SERVICE 179 | ###################### 180 | service: 181 | port: 8080 182 | 183 | serviceAccount: 184 | create: true 185 | name: "" 186 | automount: true 187 | -------------------------------------------------------------------------------- /docs/images/eoapi-k8s.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 70 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/raster/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.raster.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | {{- include "eoapi.labels" . | nindent 4 }} 7 | app.kubernetes.io/component: raster 8 | app: {{ .Release.Name }}-raster 9 | gitsha: {{ .Values.gitSha }} 10 | name: {{ .Release.Name }}-raster 11 | {{- if .Values.raster.annotations }} 12 | annotations: 13 | {{- with .Values.raster.annotations }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | progressDeadlineSeconds: 600 19 | revisionHistoryLimit: 5 20 | strategy: 21 | type: RollingUpdate 22 | rollingUpdate: 23 | maxSurge: 50% 24 | maxUnavailable: 0 25 | selector: 26 | matchLabels: 27 | {{- include "eoapi.selectorLabels" . | nindent 6 }} 28 | app.kubernetes.io/component: raster 29 | app: {{ .Release.Name }}-raster 30 | template: 31 | metadata: 32 | annotations: 33 | checksum/config: {{ include (print $.Template.BasePath "/services/raster/configmap.yaml") . | sha256sum }} 34 | labels: 35 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 36 | app.kubernetes.io/component: raster 37 | app: {{ .Release.Name }}-raster 38 | {{- with .Values.raster.settings.labels }} 39 | {{- toYaml . | nindent 8 }} 40 | {{- end }} 41 | spec: 42 | {{- include "eoapi.pgstacInitContainers" . | nindent 6 }} 43 | containers: 44 | - image: {{ .Values.raster.image.name }}:{{ .Values.raster.image.tag }} 45 | name: raster 46 | command: 47 | {{- toYaml .Values.raster.command | nindent 10 }} 48 | {{- if (and (.Values.ingress.className) (or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "traefik"))) }} 49 | - "--proxy-headers" 50 | - "--forwarded-allow-ips=*" 51 | {{- if .Values.raster.overrideRootPath}} 52 | - "--root-path={{ .Values.raster.overrideRootPath }}" 53 | {{- else }} 54 | - "--root-path={{ .Values.raster.ingress.path }}" 55 | {{- end }} 56 | {{- end }}{{/* needed for proxies and path rewrites on NLB */}} 57 | livenessProbe: 58 | tcpSocket: 59 | port: {{ .Values.service.port }} 60 | failureThreshold: 3 61 | periodSeconds: 15 62 | successThreshold: 1 63 | timeoutSeconds: 1 64 | readinessProbe: 65 | httpGet: 66 | {{- if .Values.raster.overrideRootPath}} 67 | path: {{ .Values.raster.overrideRootPath }}/healthz 68 | {{- else}} 69 | path: /healthz 70 | {{- end}} 71 | port: {{ .Values.service.port }} 72 | failureThreshold: 3 73 | periodSeconds: 15 74 | successThreshold: 1 75 | startupProbe: 76 | httpGet: 77 | {{- if .Values.raster.overrideRootPath}} 78 | path: {{ .Values.raster.overrideRootPath }}/healthz 79 | {{- else}} 80 | path: /healthz 81 | {{- end}} 82 | port: {{ .Values.service.port }} 83 | # check every sec for 1 minute 84 | periodSeconds: 1 85 | failureThreshold: 60 86 | successThreshold: 1 87 | ports: 88 | - containerPort: {{ .Values.service.port }} 89 | resources: 90 | {{- toYaml .Values.raster.settings.resources | nindent 10 }} 91 | env: 92 | {{- include "eoapi.postgresqlEnv" . | nindent 10 }} 93 | {{- include "eoapi.commonEnvVars" (dict "service" "raster" "root" .) | nindent 10 }} 94 | envFrom: 95 | - configMapRef: 96 | name: {{ .Release.Name }}-raster-envvar-configmap 97 | {{- if .Values.raster.settings.extraEnvFrom }} 98 | {{- toYaml .Values.raster.settings.extraEnvFrom | nindent 10 }} 99 | {{- end }} 100 | {{- include "eoapi.mountServiceSecrets" (dict "service" "raster" "root" .) | nindent 10 }} 101 | {{- with .Values.raster.settings.extraVolumeMounts }} 102 | volumeMounts: 103 | {{- toYaml . | nindent 10 }} 104 | {{- end }} 105 | volumes: 106 | {{- with .Values.raster.settings.extraVolumes }} 107 | {{- toYaml . | nindent 8 }} 108 | {{- end }} 109 | serviceAccountName: {{ include "eoapi.serviceAccountName" . }} 110 | {{- with .Values.raster.settings.affinity }} 111 | affinity: 112 | {{- toYaml . | nindent 8 }} 113 | {{- end }} 114 | {{- with .Values.raster.settings.tolerations }} 115 | tolerations: 116 | {{- toYaml . | nindent 8 }} 117 | {{- end }} 118 | {{- end }} 119 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/vector/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.vector.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | {{- include "eoapi.labels" . | nindent 4 }} 7 | app.kubernetes.io/component: vector 8 | app: {{ .Release.Name }}-vector 9 | gitsha: {{ .Values.gitSha }} 10 | name: {{ .Release.Name }}-vector 11 | {{- if .Values.vector.annotations }} 12 | annotations: 13 | {{- with .Values.vector.annotations }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | progressDeadlineSeconds: 600 19 | revisionHistoryLimit: 5 20 | strategy: 21 | type: RollingUpdate 22 | rollingUpdate: 23 | maxSurge: 50% 24 | maxUnavailable: 0 25 | selector: 26 | matchLabels: 27 | {{- include "eoapi.selectorLabels" . | nindent 6 }} 28 | app.kubernetes.io/component: vector 29 | app: {{ .Release.Name }}-vector 30 | template: 31 | metadata: 32 | annotations: 33 | checksum/config: {{ include (print $.Template.BasePath "/services/vector/configmap.yaml") . | sha256sum }} 34 | labels: 35 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 36 | app.kubernetes.io/component: vector 37 | app: {{ .Release.Name }}-vector 38 | {{- with .Values.vector.settings.labels }} 39 | {{- toYaml . | nindent 8 }} 40 | {{- end }} 41 | spec: 42 | {{- include "eoapi.pgstacInitContainers" . | nindent 6 }} 43 | containers: 44 | - image: {{ .Values.vector.image.name }}:{{ .Values.vector.image.tag }} 45 | name: vector 46 | command: 47 | {{- toYaml .Values.vector.command | nindent 10 }} 48 | {{- if (and (.Values.ingress.className) (or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "traefik"))) }} 49 | - "--proxy-headers" 50 | - "--forwarded-allow-ips=*" 51 | {{- if .Values.vector.overrideRootPath}} 52 | - "--root-path={{ .Values.vector.overrideRootPath }}" 53 | {{- else }} 54 | - "--root-path={{ .Values.vector.ingress.path }}" 55 | {{- end }} 56 | {{- end }}{{/* needed for proxies and path rewrites on NLB */}} 57 | livenessProbe: 58 | tcpSocket: 59 | port: {{ .Values.service.port }} 60 | failureThreshold: 3 61 | periodSeconds: 15 62 | successThreshold: 1 63 | timeoutSeconds: 1 64 | readinessProbe: 65 | httpGet: 66 | {{- if .Values.vector.overrideRootPath}} 67 | path: {{ .Values.vector.overrideRootPath }}/healthz 68 | {{- else}} 69 | path: /healthz 70 | {{- end}} 71 | port: {{ .Values.service.port }} 72 | failureThreshold: 3 73 | periodSeconds: 15 74 | successThreshold: 1 75 | startupProbe: 76 | httpGet: 77 | {{- if .Values.vector.overrideRootPath}} 78 | path: {{ .Values.vector.overrideRootPath }}/healthz 79 | {{- else}} 80 | path: /healthz 81 | {{- end}} 82 | port: {{ .Values.service.port }} 83 | # check every sec for 1 minute 84 | periodSeconds: 1 85 | failureThreshold: 60 86 | successThreshold: 1 87 | ports: 88 | - containerPort: {{ .Values.service.port }} 89 | resources: 90 | {{- toYaml .Values.vector.settings.resources | nindent 10 }} 91 | env: 92 | {{- include "eoapi.postgresqlEnv" . | nindent 10 }} 93 | {{- include "eoapi.commonEnvVars" (dict "service" "vector" "root" .) | nindent 10 }} 94 | envFrom: 95 | - configMapRef: 96 | name: {{ .Release.Name }}-vector-envvar-configmap 97 | {{- if .Values.vector.settings.extraEnvFrom }} 98 | {{- toYaml .Values.vector.settings.extraEnvFrom | nindent 10 }} 99 | {{- end }} 100 | {{- include "eoapi.mountServiceSecrets" (dict "service" "vector" "root" .) | nindent 10 }} 101 | {{- with .Values.vector.settings.extraVolumeMounts }} 102 | volumeMounts: 103 | {{- toYaml . | nindent 10 }} 104 | {{- end }} 105 | volumes: 106 | {{- with .Values.vector.settings.extraVolumes }} 107 | {{- toYaml . | nindent 8 }} 108 | {{- end }} 109 | serviceAccountName: {{ include "eoapi.serviceAccountName" . }} 110 | {{- with .Values.vector.settings.affinity }} 111 | affinity: 112 | {{- toYaml . | nindent 8 }} 113 | {{- end }} 114 | {{- with .Values.vector.settings.tolerations }} 115 | tolerations: 116 | {{- toYaml . | nindent 8 }} 117 | {{- end }} 118 | {{- end }} 119 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/multidim/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.multidim.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | {{- include "eoapi.labels" . | nindent 4 }} 7 | app.kubernetes.io/component: multidim 8 | app: {{ .Release.Name }}-multidim 9 | gitsha: {{ .Values.gitSha }} 10 | name: {{ .Release.Name }}-multidim 11 | {{- if .Values.multidim.annotations }} 12 | annotations: 13 | {{- with .Values.multidim.annotations }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | progressDeadlineSeconds: 600 19 | revisionHistoryLimit: 5 20 | strategy: 21 | type: RollingUpdate 22 | rollingUpdate: 23 | maxSurge: 50% 24 | maxUnavailable: 0 25 | selector: 26 | matchLabels: 27 | {{- include "eoapi.selectorLabels" . | nindent 6 }} 28 | app.kubernetes.io/component: multidim 29 | app: {{ .Release.Name }}-multidim 30 | template: 31 | metadata: 32 | annotations: 33 | checksum/config: {{ include (print $.Template.BasePath "/services/multidim/configmap.yaml") . | sha256sum }} 34 | labels: 35 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 36 | app.kubernetes.io/component: multidim 37 | app: {{ .Release.Name }}-multidim 38 | {{- with .Values.multidim.settings.labels }} 39 | {{- toYaml . | nindent 8 }} 40 | {{- end }} 41 | spec: 42 | {{- include "eoapi.pgstacInitContainers" . | nindent 6 }} 43 | containers: 44 | - image: {{ .Values.multidim.image.name }}:{{ .Values.multidim.image.tag }} 45 | name: multidim 46 | command: 47 | {{- toYaml .Values.multidim.command | nindent 10 }} 48 | {{- if (and (.Values.ingress.className) (or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "traefik"))) }} 49 | - "--proxy-headers" 50 | - "--forwarded-allow-ips=*" 51 | {{- if .Values.multidim.overrideRootPath}} 52 | - "--root-path={{ .Values.multidim.overrideRootPath }}" 53 | {{- else }} 54 | - "--root-path={{ .Values.multidim.ingress.path }}" 55 | {{- end }} 56 | {{- end }}{{/* needed for proxies and path rewrites on NLB */}} 57 | livenessProbe: 58 | tcpSocket: 59 | port: {{ .Values.service.port }} 60 | failureThreshold: 3 61 | periodSeconds: 15 62 | successThreshold: 1 63 | timeoutSeconds: 1 64 | readinessProbe: 65 | httpGet: 66 | {{- if .Values.multidim.overrideRootPath}} 67 | path: {{ .Values.multidim.overrideRootPath }}/healthz 68 | {{- else}} 69 | path: /healthz 70 | {{- end}} 71 | port: {{ .Values.service.port }} 72 | failureThreshold: 3 73 | periodSeconds: 15 74 | successThreshold: 1 75 | startupProbe: 76 | httpGet: 77 | {{- if .Values.multidim.overrideRootPath}} 78 | path: {{ .Values.multidim.overrideRootPath }}/healthz 79 | {{- else}} 80 | path: /healthz 81 | {{- end}} 82 | port: {{ .Values.service.port }} 83 | # check every sec for 1 minute 84 | periodSeconds: 1 85 | failureThreshold: 60 86 | successThreshold: 1 87 | ports: 88 | - containerPort: {{ .Values.service.port }} 89 | resources: 90 | {{- toYaml .Values.multidim.settings.resources | nindent 10 }} 91 | env: 92 | {{- include "eoapi.postgresqlEnv" . | nindent 10 }} 93 | {{- include "eoapi.commonEnvVars" (dict "service" "multidim" "root" .) | nindent 10 }} 94 | envFrom: 95 | - configMapRef: 96 | name: {{ .Release.Name }}-multidim-envvar-configmap 97 | {{- if .Values.multidim.settings.extraEnvFrom }} 98 | {{- toYaml .Values.multidim.settings.extraEnvFrom | nindent 10 }} 99 | {{- end }} 100 | {{- include "eoapi.mountServiceSecrets" (dict "service" "multidim" "root" .) | nindent 10 }} 101 | {{- with .Values.multidim.settings.extraVolumeMounts }} 102 | volumeMounts: 103 | {{- toYaml . | nindent 10 }} 104 | {{- end }} 105 | volumes: 106 | {{- with .Values.multidim.settings.extraVolumes }} 107 | {{- toYaml . | nindent 8 }} 108 | {{- end }} 109 | serviceAccountName: {{ include "eoapi.serviceAccountName" . }} 110 | {{- with .Values.multidim.settings.affinity }} 111 | affinity: 112 | {{- toYaml . | nindent 8 }} 113 | {{- end }} 114 | {{- with .Values.multidim.settings.tolerations }} 115 | tolerations: 116 | {{- toYaml . | nindent 8 }} 117 | {{- end }} 118 | {{- end }} 119 | -------------------------------------------------------------------------------- /charts/eoapi/initdb-data/samples/my_data.sql: -------------------------------------------------------------------------------- 1 | SET standard_conforming_strings = OFF; 2 | DROP TABLE IF EXISTS "public"."my_data" CASCADE; 3 | DELETE FROM geometry_columns WHERE f_table_name = 'my_data' AND f_table_schema = 'public'; 4 | BEGIN; 5 | CREATE TABLE "public"."my_data" ( "ogc_fid" SERIAL, CONSTRAINT "my_data_pk" PRIMARY KEY ("ogc_fid") ); 6 | SELECT AddGeometryColumn('public','my_data','geom',4326,'GEOMETRY',2); 7 | CREATE INDEX "my_data_geom_geom_idx" ON "public"."my_data" USING GIST ("geom"); 8 | ALTER TABLE "public"."my_data" ADD COLUMN "id" VARCHAR; 9 | ALTER TABLE "public"."my_data" ADD COLUMN "datetime" TIMESTAMP; 10 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E6100000010000001B0000003670CC05599B25C03A92CB7F483F54408907944DB9F221C0D9CEF753E315544069D68681BE5B22C0355D864BD1145440984C2580F45C27C062327530C20754409CB396CA942C30C08E6EC42E50F05340F32225E11DCB30C07C98C2D614ED5340075F984C15FC30C0075F984C15EC53400AA1BD9D6AD732C03439A530F50B5440D8BFC6C0170533C00414E74C050F54407650100F7C0E33C0B199D586A60F5440A01BF45DE29634C0B61719B9F6295440838D3D254B5D35C0DC611EC044375440B8A6A26802F135C06705618A2C4154407CBD21E2CF3136C09B1B77FC844554402CD49AE61D3736C076711B0DE045544039117CFD650136C001AEC11005475440DC27DD0AB9C935C0F45E61C1344854406182187FE9BA35C03AF2E08A854854400736A0D273F130C050CF32FAA1625440ED137AA9497230C0441F419D576554401D9FC06CB06E2BC0B1930B183C745440017C2AECC5F92AC01E2006F67A7554401895D40968822AC0986E1283C07654405D44620EE0782AC0E00B92AC54765440FAACE2F3F95C27C0CDCE93B2275354400D2FBCF61DD226C0385BB99C044D54403670CC05599B25C03A92CB7F483F5440', '0', '2004-10-19 10:23:54'); 11 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E61000000100000019000000984067B8143E3DC043C2B8B8F40B5440ACEF9DFAC14B3DC0E950B3BEBB0C544070CE88D2DE503DC01B2FDD24060D544034C8A112A4243DC064CC7707650E54409232AD9551103DC079704A40060F5440A630DBCBFBF43CC0E22ABE1BDF0F5440AC95A5A7DFA638C09E34007606325440FE987A2A9D7238C05D165F0DA5335440D1BF9E64C80A38C0FF6D3AC6DC3654409ACC3E07335D36C0578150C82C4454407CBD21E2CF3136C09B1B77FC8445544039117CFD650136C001AEC110054754401EA7E8482ECF35C07F6ABC7493485440DC27DD0AB9C935C0F45E61C134485440A2F3387764C135C09C775737A44754405526CE34BBDB34C047F7C465133854408DF37646C5EA33C0F10FDC85BE2754406D6485236BA431C08C72AF36460054403EE8D9ACFA9C30C07CF2B0506BEE5340F32225E11DCB30C07C98C2D614ED5340FE41CA2BA27737C016B27D9C8ABB5340C442AD69DEA137C05F07CE1951BA5340F9CBEEC9C30A38C07E078C8947C05340898D7238194D38C059C5B4D10CC45340984067B8143E3DC043C2B8B8F40B5440', '1', '2004-10-20 10:23:54'); 12 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E61000000100000013000000C0155236C40A38C052F1FFE1D8C75340B244B5A16EC837C014EBB5CD0CC4534073D712F2414F37C0D3BCE3141DBD5340FE41CA2BA27737C016B27D9C8ABB5340A2728C64C30A38C03BFB4402D0B553400C6AB4D7723A3DC0BDA377861D82534058CA32C4B15E3DC062105839B48053402A2097D1F19641C0EAE96F4E58CC5340F0A7C64B379941C07F6ABC7493CC5340E11AE2531A8741C01F2670501DCE5340CED31A45F57241C03EC92059D3CF534009E08D47F1E83FC0EAC3384350F05340DFE755925F713EC036A2858243005440ACEF9DFAC14B3DC0E950B3BEBB0C544034C8A112A4243DC064CC7707650E5440F602E719D4063DC0AE877727A90F54400A68226C78FA3CC0234A7B832F105440A630DBCBFBF43CC0E22ABE1BDF0F5440C0155236C40A38C052F1FFE1D8C75340', '2', '2004-10-21 10:23:54'); 13 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E610000001000000110000001B2CBE53855542C051F99E0E805D534049A5CD2EAE0644C03857A7D846865340462575029A0844C0A60A46257586534063B4EEABC4F943C08D992E511D8853409C72BC6BC5E843C0920AAB5C038A5340721D3749863342C03D0220C7DABA53402A2097D1F19641C0EAE96F4E58CC5340E11AE2531A8741C01F2670501DCE534068226C787A7541C0075F984C15D05340CED31A45F57241C03EC92059D3CF534048E17A14AE173DC06B2BF697DD8353400C6AB4D7723A3DC0BDA377861D825340A03E0335AD283FC0314A54553C6953409C6F1F2DEA1541C00EA6095E6A425340BEC11726532541C0BE9F1A2FDD405340EB51B81E853342C0302C67AA4C5A53401B2CBE53855542C051F99E0E805D5340', '3', '2004-10-22 10:23:54'); 14 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E610000001000000110000000A4C8422590E46C0B656FB86F03B5340D5E76A2BF60F46C0075F984C153C5340FA28B2217F0346C0CE0A257ADB3D5340BEE6287052F545C01AA33BF2DF3F5340F25A937BB7D244C009CB92853C69534049A5CD2EAE0644C03857A7D84686534063B4EEABC4F943C08D992E511D88534034A2B437F8EA43C0F54A5986388A53409C72BC6BC5E843C0920AAB5C038A534050AF9465883342C0363B85F6B5605340D43E0032881142C02A5884BF7F5D5340F4FDD478E90641C007F01648504453409C6F1F2DEA1541C00EA6095E6A4253404E4E9C88873342C06DC6E4C7471E53403EDF52396E3443C0DC9EAF2DC7FD524044696FF0854143C032772D211FFC52400A4C8422590E46C0B656FB86F03B5340', '4', '2004-10-23 10:23:54'); 15 | INSERT INTO "public"."my_data" ("geom" , "id", "datetime") VALUES ('0103000020E6100000010000000D000000BBE9944235C347C0EBF06E7961EE52406ADE718A8EC447C0D122DBF97EEE5240942D6301ECB947C05B59871F60F0524086CAEEF61AAE47C0BDEF3BBB76F252400A4C8422590E46C0B656FB86F03B5340FA28B2217F0346C0CE0A257ADB3D534057EC2FBB27F745C02B1895D409405340BEE6287052F545C01AA33BF2DF3F53401D386744692743C07958A835CDFF52403EDF52396E3443C0DC9EAF2DC7FD5240B9E39237FD0645C0574B4E2543B552400AD7A3703D1245C03A234A7B83B35240BBE9944235C347C0EBF06E7961EE5240', '5', '2004-10-24 10:23:54'); 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /charts/eoapi/templates/services/stac/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.stac.enabled }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | {{- include "eoapi.labels" . | nindent 4 }} 7 | app.kubernetes.io/component: stac 8 | app: {{ .Release.Name }}-stac 9 | gitsha: {{ .Values.gitSha }} 10 | name: {{ .Release.Name }}-stac 11 | {{- if .Values.stac.annotations }} 12 | annotations: 13 | {{- with .Values.stac.annotations }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | progressDeadlineSeconds: 600 19 | revisionHistoryLimit: 5 20 | strategy: 21 | type: RollingUpdate 22 | rollingUpdate: 23 | maxSurge: 50% 24 | maxUnavailable: 0 25 | selector: 26 | matchLabels: 27 | {{- include "eoapi.selectorLabels" . | nindent 6 }} 28 | app.kubernetes.io/component: stac 29 | app: {{ .Release.Name }}-stac 30 | template: 31 | metadata: 32 | annotations: 33 | checksum/config: {{ include (print $.Template.BasePath "/services/stac/configmap.yaml") . | sha256sum }} 34 | labels: 35 | {{- include "eoapi.selectorLabels" . | nindent 8 }} 36 | app.kubernetes.io/component: stac 37 | app: {{ .Release.Name }}-stac 38 | {{- with .Values.stac.settings.labels }} 39 | {{- toYaml . | nindent 8 }} 40 | {{- end }} 41 | spec: 42 | {{- include "eoapi.pgstacInitContainers" . | nindent 6 }} 43 | containers: 44 | - image: {{ .Values.stac.image.name }}:{{ .Values.stac.image.tag }} 45 | name: stac 46 | command: 47 | {{- toYaml .Values.stac.command | nindent 10 }} 48 | {{- if (and (.Values.ingress.className) (or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "traefik"))) }} 49 | - "--proxy-headers" 50 | - "--forwarded-allow-ips=*" 51 | {{- if hasKey .Values.stac "overrideRootPath" }} 52 | {{- if ne .Values.stac.overrideRootPath "" }} 53 | - "--root-path={{ .Values.stac.overrideRootPath }}" 54 | {{- end }} 55 | {{- else }} 56 | - "--root-path={{ .Values.stac.ingress.path }}" 57 | {{- end }} 58 | {{- end }}{{/* needed for proxies and path rewrites on NLB */}} 59 | livenessProbe: 60 | tcpSocket: 61 | port: {{ .Values.service.port }} 62 | failureThreshold: 3 63 | periodSeconds: 15 64 | successThreshold: 1 65 | timeoutSeconds: 1 66 | readinessProbe: 67 | httpGet: 68 | {{- if hasKey .Values.stac "overrideRootPath" }} 69 | {{- if ne .Values.stac.overrideRootPath "" }} 70 | path: {{ .Values.stac.overrideRootPath }}/_mgmt/ping 71 | {{- else }} 72 | path: /_mgmt/ping 73 | {{- end }} 74 | {{- else}} 75 | path: /_mgmt/ping 76 | {{- end}} 77 | port: {{ .Values.service.port }} 78 | failureThreshold: 3 79 | periodSeconds: 15 80 | successThreshold: 1 81 | startupProbe: 82 | httpGet: 83 | {{- if hasKey .Values.stac "overrideRootPath" }} 84 | {{- if ne .Values.stac.overrideRootPath "" }} 85 | path: {{ .Values.stac.overrideRootPath }}/_mgmt/ping 86 | {{- else }} 87 | path: /_mgmt/ping 88 | {{- end }} 89 | {{- else}} 90 | path: /_mgmt/ping 91 | {{- end}} 92 | port: {{ .Values.service.port }} 93 | # check every sec for 1 minute 94 | periodSeconds: 1 95 | failureThreshold: 60 96 | successThreshold: 1 97 | ports: 98 | - containerPort: {{ .Values.service.port }} 99 | resources: 100 | {{- toYaml .Values.stac.settings.resources | nindent 10 }} 101 | env: 102 | {{- include "eoapi.postgresqlEnv" . | nindent 10 }} 103 | {{- include "eoapi.commonEnvVars" (dict "service" "stac" "root" .) | nindent 10 }} 104 | envFrom: 105 | - configMapRef: 106 | name: {{ .Release.Name }}-stac-envvar-configmap 107 | {{- if .Values.stac.settings.extraEnvFrom }} 108 | {{- toYaml .Values.stac.settings.extraEnvFrom | nindent 10 }} 109 | {{- end }} 110 | {{- include "eoapi.mountServiceSecrets" (dict "service" "stac" "root" .) | nindent 10 }} 111 | {{- with .Values.stac.settings.extraVolumeMounts }} 112 | volumeMounts: 113 | {{- toYaml . | nindent 10 }} 114 | {{- end }} 115 | volumes: 116 | {{- with .Values.stac.settings.extraVolumes }} 117 | {{- toYaml . | nindent 8 }} 118 | {{- end }} 119 | serviceAccountName: {{ include "eoapi.serviceAccountName" . }} 120 | {{- with .Values.stac.settings.affinity }} 121 | affinity: 122 | {{- toYaml . | nindent 8 }} 123 | {{- end }} 124 | {{- with .Values.stac.settings.tolerations }} 125 | tolerations: 126 | {{- toYaml . | nindent 8 }} 127 | {{- end }} 128 | {{- end }} 129 | -------------------------------------------------------------------------------- /scripts/test/integration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # eoAPI Integration Tests Script 4 | 5 | set -euo pipefail 6 | 7 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 8 | PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" 9 | 10 | source "${SCRIPT_DIR}/../lib/common.sh" 11 | 12 | NAMESPACE="${NAMESPACE:-eoapi}" 13 | RELEASE_NAME="${RELEASE_NAME:-eoapi}" 14 | 15 | run_integration_tests() { 16 | local pytest_args="${1:-}" 17 | 18 | log_info "Running integration tests..." 19 | 20 | check_requirements python3 kubectl || return 1 21 | validate_cluster || return 1 22 | 23 | log_info "Installing Python test dependencies..." 24 | python3 -m pip install --user -r "${PROJECT_ROOT}/tests/requirements.txt" >/dev/null 2>&1 || { 25 | log_warn "Could not install test dependencies automatically" 26 | log_info "Try manually: pip install -r tests/requirements.txt" 27 | } 28 | 29 | if ! kubectl get deployment -n "$NAMESPACE" -l "app.kubernetes.io/instance=$RELEASE_NAME" &>/dev/null; then 30 | log_error "eoAPI deployment not found (release: $RELEASE_NAME, namespace: $NAMESPACE)" 31 | log_info "Deploy first with: eoapi deployment run" 32 | return 1 33 | fi 34 | 35 | cd "$PROJECT_ROOT" 36 | 37 | export RELEASE_NAME="$RELEASE_NAME" 38 | export NAMESPACE="$NAMESPACE" 39 | 40 | log_info "Setting up test environment..." 41 | 42 | local ingress_host 43 | local actual_host 44 | 45 | ingress_host=$(kubectl get ingress -n "$NAMESPACE" -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "") 46 | 47 | if [[ -z "$ingress_host" ]]; then 48 | log_info "No ingress host configured in Kubernetes, will use localhost" 49 | actual_host="localhost" 50 | else 51 | log_info "Ingress configured with host: $ingress_host" 52 | log_info "Testing connectivity to http://$ingress_host/stac..." 53 | # Check if the ingress host is reachable 54 | if curl -s -f -m 2 "http://$ingress_host/stac" >/dev/null 2>&1; then 55 | log_success "Successfully connected to $ingress_host" 56 | actual_host="$ingress_host" 57 | else 58 | log_warn "Cannot reach $ingress_host (this is expected in CI with k3s)" 59 | log_info "Falling back to localhost for service access" 60 | actual_host="localhost" 61 | fi 62 | fi 63 | 64 | log_info "Final endpoint host selection: $actual_host" 65 | 66 | log_info "Verifying services are ready..." 67 | local service_ready=false 68 | local retries=10 69 | while [ $retries -gt 0 ]; do 70 | if curl -s -f "http://$actual_host/stac" >/dev/null 2>&1 && \ 71 | curl -s -f "http://$actual_host/raster/healthz" >/dev/null 2>&1 && \ 72 | curl -s -f "http://$actual_host/vector/healthz" >/dev/null 2>&1; then 73 | service_ready=true 74 | log_info "All services are responding correctly" 75 | break 76 | fi 77 | retries=$((retries - 1)) 78 | if [ $retries -gt 0 ]; then 79 | log_debug "Waiting for services to be ready... (retries left: $retries)" 80 | sleep 3 81 | fi 82 | done 83 | 84 | if [ "$service_ready" = false ]; then 85 | log_warn "Some services may not be fully ready" 86 | fi 87 | 88 | log_info "Ensuring all pods are ready..." 89 | for service in stac raster vector; do 90 | local deployment="${RELEASE_NAME}-${service}" 91 | kubectl wait --for=condition=available deployment/"${deployment}" -n "$NAMESPACE" --timeout=60s 2>/dev/null || \ 92 | log_warn "Deployment ${deployment} may not be fully ready" 93 | done 94 | 95 | log_info "Allowing services to stabilize..." 96 | sleep 5 97 | 98 | export STAC_ENDPOINT="${STAC_ENDPOINT:-http://$actual_host/stac}" 99 | export RASTER_ENDPOINT="${RASTER_ENDPOINT:-http://$actual_host/raster}" 100 | export VECTOR_ENDPOINT="${VECTOR_ENDPOINT:-http://$actual_host/vector}" 101 | export MOCK_OIDC_ENDPOINT="${MOCK_OIDC_ENDPOINT:-http://$actual_host/mock-oidc}" 102 | 103 | log_info "Test endpoints configured:" 104 | log_info " STAC: $STAC_ENDPOINT" 105 | log_info " Raster: $RASTER_ENDPOINT" 106 | log_info " Vector: $VECTOR_ENDPOINT" 107 | log_info " Mock OIDC: $MOCK_OIDC_ENDPOINT" 108 | 109 | log_info "Running service warmup..." 110 | for endpoint in "$STAC_ENDPOINT" "$RASTER_ENDPOINT/healthz" "$VECTOR_ENDPOINT/healthz"; do 111 | for _ in {1..3}; do 112 | curl -s -f "$endpoint" >/dev/null 2>&1 || true 113 | sleep 0.5 114 | done 115 | done 116 | 117 | local cmd="python3 -m pytest tests/integration" 118 | [[ "$DEBUG_MODE" == "true" ]] && cmd="$cmd -v --tb=short" 119 | [[ -n "$pytest_args" ]] && cmd="$cmd $pytest_args" 120 | 121 | log_debug "Running: $cmd" 122 | 123 | if eval "$cmd"; then 124 | log_success "Integration tests passed" 125 | return 0 126 | else 127 | log_error "Integration tests failed" 128 | return 1 129 | fi 130 | } 131 | 132 | run_integration_tests "$@" 133 | -------------------------------------------------------------------------------- /eoapi-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # eoAPI CLI - Main Entry Point 4 | # This script provides a unified interface to all eoAPI management commands 5 | 6 | set -euo pipefail 7 | 8 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | SCRIPTS_DIR="${SCRIPT_DIR}/scripts" 10 | 11 | source "${SCRIPTS_DIR}/lib/common.sh" 12 | 13 | readonly VERSION="0.1.0" 14 | 15 | readonly COMMANDS=( 16 | "cluster" 17 | "deployment" 18 | "test" 19 | "ingest" 20 | "docs" 21 | ) 22 | 23 | show_help() { 24 | cat < [ARGS] 31 | 32 | OPTIONS: 33 | -h, --help Show this help message 34 | -v, --version Show version information 35 | --debug Enable debug output 36 | 37 | COMMANDS: 38 | cluster Manage local Kubernetes clusters for development 39 | deployment Deploy and manage eoAPI instances 40 | test Run tests (helm, integration, autoscaling) 41 | ingest Load sample data into eoAPI services 42 | docs Generate and serve documentation 43 | 44 | Use 'eoapi-cli --help' for more information about a specific command. 45 | 46 | EXAMPLES: 47 | # Set up a local development cluster 48 | eoapi-cli cluster start 49 | 50 | # Deploy eoAPI to the cluster 51 | eoapi-cli deployment install 52 | 53 | # Run all tests 54 | eoapi-cli test all 55 | 56 | # Run integration tests only 57 | eoapi-cli test integration 58 | 59 | # Run autoscaling tests only 60 | eoapi-cli test autoscaling 61 | 62 | # Ingest sample data 63 | eoapi-cli ingest sample-data 64 | 65 | # Serve documentation locally 66 | eoapi-cli docs serve 67 | 68 | For more information, visit: https://github.com/developmentseed/eoapi-k8s 69 | EOF 70 | } 71 | 72 | show_version() { 73 | echo "eoAPI CLI v${VERSION}" 74 | echo "Copyright (c) Development Seed" 75 | } 76 | 77 | validate_command() { 78 | local cmd="$1" 79 | 80 | for valid_cmd in "${COMMANDS[@]}"; do 81 | if [[ "$cmd" == "$valid_cmd" ]]; then 82 | return 0 83 | fi 84 | done 85 | 86 | return 1 87 | } 88 | 89 | get_command_script() { 90 | local cmd="$1" 91 | 92 | case "$cmd" in 93 | cluster) 94 | echo "${SCRIPTS_DIR}/cluster.sh" 95 | ;; 96 | deployment) 97 | echo "${SCRIPTS_DIR}/deployment.sh" 98 | ;; 99 | test) 100 | echo "${SCRIPTS_DIR}/test.sh" 101 | ;; 102 | ingest) 103 | echo "${SCRIPTS_DIR}/ingest.sh" 104 | ;; 105 | docs) 106 | echo "${SCRIPTS_DIR}/docs.sh" 107 | ;; 108 | *) 109 | return 1 110 | ;; 111 | esac 112 | } 113 | 114 | execute_command() { 115 | local cmd="$1" 116 | shift 117 | 118 | local script_path 119 | script_path=$(get_command_script "$cmd") 120 | 121 | if [[ ! -f "$script_path" ]]; then 122 | log_error "Command script not found: $script_path" 123 | log_info "The '$cmd' command may not be implemented yet." 124 | exit 1 125 | fi 126 | 127 | if [[ ! -x "$script_path" ]]; then 128 | log_warn "Making script executable: $script_path" 129 | chmod +x "$script_path" 130 | fi 131 | 132 | # Execute the command script with remaining arguments 133 | exec "$script_path" "$@" 134 | } 135 | 136 | main() { 137 | # Handle no arguments 138 | if [[ $# -eq 0 ]]; then 139 | show_help 140 | exit 0 141 | fi 142 | 143 | # Parse global options 144 | while [[ $# -gt 0 ]]; do 145 | case "$1" in 146 | -h|--help) 147 | show_help 148 | exit 0 149 | ;; 150 | -v|--version) 151 | show_version 152 | exit 0 153 | ;; 154 | --debug) 155 | export DEBUG_MODE=true 156 | shift 157 | ;; 158 | -*) 159 | log_error "Unknown option: $1" 160 | echo "Run 'eoapi-cli --help' for usage information" 161 | exit 1 162 | ;; 163 | *) 164 | # This should be the command 165 | break 166 | ;; 167 | esac 168 | done 169 | 170 | if [[ $# -eq 0 ]]; then 171 | log_error "No command specified" 172 | echo "Run 'eoapi-cli --help' for usage information" 173 | exit 1 174 | fi 175 | 176 | local command="$1" 177 | shift 178 | 179 | if ! validate_command "$command"; then 180 | log_error "Invalid command: $command" 181 | echo "" 182 | echo "Available commands:" 183 | for cmd in "${COMMANDS[@]}"; do 184 | echo " - $cmd" 185 | done 186 | echo "" 187 | echo "Run 'eoapi-cli --help' for usage information" 188 | exit 1 189 | fi 190 | 191 | execute_command "$command" "$@" 192 | } 193 | 194 | main "$@" 195 | -------------------------------------------------------------------------------- /docs/argocd.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ArgoCD Integration" 3 | description: "Guide for deploying eoAPI with ArgoCD, including sync waves, hooks, and best practices" 4 | external_links: 5 | - name: "ArgoCD Sync Phases and Waves" 6 | url: "https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/" 7 | - name: "ArgoCD Resource Hooks" 8 | url: "https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/" 9 | --- 10 | 11 | # ArgoCD Integration 12 | 13 | This guide covers deploying eoAPI with ArgoCD, focusing on sync order management, database initialization, and troubleshooting common issues. 14 | 15 | ## Overview 16 | 17 | eoAPI includes database initialization jobs that must complete before application services start. ArgoCD's sync waves and hooks provide fine-grained control over resource deployment order. 18 | 19 | ## Quick Start 20 | 21 | For most ArgoCD deployments, add these annotations to ensure proper sync order: 22 | 23 | ```yaml 24 | pgstacBootstrap: 25 | jobAnnotations: 26 | argocd.argoproj.io/hook: "PreSync" 27 | argocd.argoproj.io/sync-wave: "-1" 28 | argocd.argoproj.io/hook-delete-policy: "HookSucceeded" 29 | ``` 30 | 31 | ## Database Bootstrap Jobs 32 | 33 | eoAPI includes several database initialization jobs: 34 | 35 | | Job | Purpose | Dependencies | 36 | |:----|:--------|:-------------| 37 | | `pgstac-migrate` | Database schema migration | PostgreSQL ready | 38 | | `pgstac-load-samples` | Load sample data | Schema migrated | 39 | | `pgstac-load-queryables` | Configure queryables | Schema migrated | 40 | 41 | ### Job Execution Order 42 | 43 | The jobs use Helm hook weights to ensure proper ordering: 44 | 45 | 1. **pgstac-migrate** (weight: `-5`) - Creates database schema 46 | 2. **pgstac-load-samples** (weight: `-4`) - Loads sample collections/items 47 | 3. **pgstac-load-queryables** (weight: `-3`) - Configures search queryables 48 | 49 | ## ArgoCD Sync Configuration 50 | 51 | ### Sync Phases 52 | 53 | Use **PreSync** for database initialization jobs to ensure they complete before application deployment: 54 | 55 | ```yaml 56 | pgstacBootstrap: 57 | jobAnnotations: 58 | argocd.argoproj.io/hook: "PreSync" 59 | ``` 60 | 61 | #### Why PreSync? 62 | 63 | - **Database First**: Schema must exist before application services start 64 | - **Prevents Race Conditions**: Services won't start until database is ready 65 | - **Follows Best Practices**: Standard pattern for database migrations 66 | - **Dependency Management**: Explicit ordering prevents startup failures 67 | 68 | ### Sync Waves 69 | 70 | Control execution order within phases using sync waves: 71 | 72 | ```yaml 73 | pgstacBootstrap: 74 | jobAnnotations: 75 | argocd.argoproj.io/sync-wave: "-1" # Run before wave 0 (default) 76 | ``` 77 | 78 | #### Wave Strategy 79 | 80 | | Wave | Resources | Purpose | 81 | |:-----|:----------|:--------| 82 | | `-2` | Secrets, ConfigMaps | Prerequisites | 83 | | `-1` | Database jobs | Schema initialization | 84 | | `0` | Applications (default) | Main services | 85 | | `1` | Ingress, monitoring | Post-deployment | 86 | 87 | ### Cleanup Policies 88 | 89 | Configure job cleanup after successful execution: 90 | 91 | ```yaml 92 | pgstacBootstrap: 93 | jobAnnotations: 94 | argocd.argoproj.io/hook-delete-policy: "HookSucceeded" 95 | ``` 96 | 97 | #### Available Policies 98 | 99 | | Policy | Behavior | 100 | |:-------|:---------| 101 | | `HookSucceeded` | Delete after successful completion | 102 | | `HookFailed` | Delete after failure | 103 | | `BeforeHookCreation` | Delete before creating new hook | 104 | 105 | ## Complete Configuration Example 106 | 107 | ```yaml 108 | # Application values for ArgoCD deployment 109 | apiVersion: argoproj.io/v1alpha1 110 | kind: Application 111 | metadata: 112 | name: eoapi 113 | namespace: argocd 114 | spec: 115 | project: default 116 | source: 117 | repoURL: https://devseed.com/eoapi-k8s/ 118 | chart: eoapi 119 | targetRevision: "0.8.1" 120 | helm: 121 | values: | 122 | # Required values 123 | gitSha: "abc123def456" 124 | 125 | # Database initialization with ArgoCD integration 126 | pgstacBootstrap: 127 | enabled: true 128 | jobAnnotations: 129 | argocd.argoproj.io/hook: "PreSync" 130 | argocd.argoproj.io/sync-wave: "-1" 131 | argocd.argoproj.io/hook-delete-policy: "HookSucceeded" 132 | 133 | # Service configuration 134 | apiServices: ["stac", "raster", "vector"] 135 | 136 | # Ingress setup 137 | ingress: 138 | enabled: true 139 | className: "nginx" 140 | host: "eoapi.example.com" 141 | 142 | destination: 143 | server: https://kubernetes.default.svc 144 | namespace: eoapi 145 | 146 | syncPolicy: 147 | automated: 148 | prune: true 149 | selfHeal: true 150 | syncOptions: 151 | - "CreateNamespace=true" 152 | - "RespectIgnoreAnnotations=true" 153 | ``` 154 | 155 | ## Further Reading 156 | 157 | - [ArgoCD Sync Phases and Waves](https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/) 158 | - [ArgoCD Resource Hooks](https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/) 159 | - [Helm Install Process](../helm-install.md) 160 | - [Configuration Options](../configuration.md) 161 | -------------------------------------------------------------------------------- /tests/integration/test_vector.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import httpx 5 | 6 | timeout = httpx.Timeout(15.0, connect=60.0) 7 | if bool(os.getenv("IGNORE_SSL_VERIFICATION", False)): 8 | client = httpx.Client(timeout=timeout, verify=False) 9 | else: 10 | client = httpx.Client(timeout=timeout) 11 | 12 | 13 | def test_vector_api(vector_endpoint: str) -> None: 14 | """test vector.""" 15 | # landing 16 | resp = client.get(f"{vector_endpoint}/") 17 | assert resp.status_code == 200 18 | assert resp.headers["content-type"] == "application/json" 19 | assert resp.json()["links"] 20 | 21 | # conformance 22 | resp = client.get(f"{vector_endpoint}/conformance") 23 | assert resp.status_code == 200 24 | assert resp.headers["content-type"] == "application/json" 25 | assert resp.json()["conformsTo"] 26 | 27 | # refresh to get newest catalog 28 | resp = client.get(f"{vector_endpoint}/refresh") 29 | assert resp.status_code == 200 30 | 31 | # collections 32 | resp = client.get(f"{vector_endpoint}/collections") 33 | assert resp.status_code == 200 34 | assert resp.headers["content-type"] == "application/json" 35 | 36 | assert list(resp.json()) == [ 37 | "links", 38 | "numberMatched", 39 | "numberReturned", 40 | "collections", 41 | ] 42 | 43 | total_timeout = 60 * 2 44 | start_time = time.time() 45 | while True: 46 | collections_data = resp.json() 47 | current_count = collections_data.get("numberMatched", 0) 48 | print(f"Current collections count: {current_count}/7") 49 | 50 | if current_count == 7: 51 | break 52 | 53 | elapsed_time = time.time() - start_time 54 | if elapsed_time > total_timeout: 55 | print( 56 | f"Timeout exceeded after {elapsed_time:.1f}s. Expected 7 collections, got {current_count}" 57 | ) 58 | if "collections" in collections_data: 59 | available_collections = [ 60 | c.get("id", "unknown") 61 | for c in collections_data["collections"] 62 | ] 63 | print(f"Available collections: {available_collections}") 64 | assert False, ( 65 | f"Expected 7 collections but found {current_count} after {elapsed_time:.1f}s timeout" 66 | ) 67 | 68 | time.sleep(10) 69 | resp = client.get(f"{vector_endpoint}/collections") 70 | 71 | collections_data = resp.json() 72 | matched_count = collections_data.get("numberMatched", 0) 73 | returned_count = collections_data.get("numberReturned", 0) 74 | 75 | assert matched_count == 7, ( 76 | f"Expected 7 matched collections, got {matched_count}. " 77 | f"Available: {[c.get('id', 'unknown') for c in collections_data.get('collections', [])]}" 78 | ) 79 | assert returned_count == 7, ( 80 | f"Expected 7 returned collections, got {returned_count}" 81 | ) 82 | 83 | collections = resp.json()["collections"] 84 | ids = [c["id"] for c in collections] 85 | # 3 Functions 86 | assert "public.st_squaregrid" in ids 87 | assert "public.st_hexagongrid" in ids 88 | assert "public.st_subdivide" in ids 89 | # 1 public table 90 | assert "public.my_data" in ids 91 | 92 | # collection 93 | resp = client.get(f"{vector_endpoint}/collections/public.my_data") 94 | assert resp.status_code == 200 95 | assert resp.headers["content-type"] == "application/json" 96 | assert resp.json()["links"] 97 | assert resp.json()["itemType"] == "feature" 98 | 99 | # items 100 | resp = client.get(f"{vector_endpoint}/collections/public.my_data/items") 101 | assert resp.status_code == 200 102 | assert resp.headers["content-type"] == "application/geo+json" 103 | items = resp.json()["features"] 104 | assert len(items) == 6 105 | 106 | # limit 107 | resp = client.get( 108 | f"{vector_endpoint}/collections/public.my_data/items", 109 | params={"limit": 1}, 110 | ) 111 | assert resp.status_code == 200 112 | items = resp.json()["features"] 113 | assert len(items) == 1 114 | 115 | # intersects 116 | resp = client.get( 117 | f"{vector_endpoint}/collections/public.my_data/items", 118 | params={"bbox": "-180,0,0,90"}, 119 | ) 120 | assert resp.status_code == 200 121 | items = resp.json()["features"] 122 | assert len(items) == 6 123 | 124 | # item 125 | resp = client.get(f"{vector_endpoint}/collections/public.my_data/items/1") 126 | assert resp.status_code == 200 127 | item = resp.json() 128 | assert item["id"] == 1 129 | 130 | # OGC Tiles 131 | resp = client.get( 132 | f"{vector_endpoint}/collections/public.my_data/tiles/WebMercatorQuad/0/0/0" 133 | ) 134 | assert resp.status_code == 200 135 | 136 | resp = client.get( 137 | f"{vector_endpoint}/collections/public.my_data/tiles/WebMercatorQuad/tilejson.json" 138 | ) 139 | assert resp.status_code == 200 140 | 141 | resp = client.get(f"{vector_endpoint}/tileMatrixSets") 142 | assert resp.status_code == 200 143 | 144 | resp = client.get(f"{vector_endpoint}/tileMatrixSets/WebMercatorQuad") 145 | assert resp.status_code == 200 146 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "description": "Renovate configuration for eoAPI Kubernetes charts", 4 | "extends": [ 5 | "config:recommended", 6 | ":dependencyDashboard", 7 | ":semanticCommits", 8 | ":separatePatchReleases" 9 | ], 10 | "timezone": "UTC", 11 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 12 | "prConcurrentLimit": 10, 13 | "prHourlyLimit": 5, 14 | "commitMessagePrefix": "", 15 | "commitMessageAction": "Updated", 16 | "commitMessageTopic": "{{depName}}", 17 | "commitMessageExtra": "to {{newVersion}}.", 18 | "branchPrefix": "renovate/", 19 | "forkProcessing": "disabled", 20 | "assigneesFromCodeOwners": true, 21 | "reviewersFromCodeOwners": true, 22 | "platformAutomerge": false, 23 | "automerge": false, 24 | "ignoreDeps": [], 25 | "vulnerabilityAlerts": { 26 | "enabled": true 27 | }, 28 | "osvVulnerabilityAlerts": true, 29 | "packageRules": [ 30 | {"matchCategories": ["docker"], "enabled": true, "pinDigests": false}, 31 | { 32 | "description": "Helm chart dependencies - individual PRs", 33 | "matchManagers": ["helmv3"], 34 | "matchFileNames": ["charts/**/Chart.yaml"], 35 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 36 | "automerge": false, 37 | "platformAutomerge": false, 38 | "labels": ["dependencies", "helm"] 39 | }, 40 | { 41 | "description": "STAC ecosystem container images - individual PRs", 42 | "matchManagers": ["helm-values"], 43 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 44 | "matchPackageNames": [ 45 | "/ghcr.io/stac-utils/pgstac-pypgstac/", 46 | "/ghcr.io/stac-utils/titiler-pgstac/", 47 | "/ghcr.io/stac-utils/stac-fastapi-pgstac/" 48 | ], 49 | "automerge": false, 50 | "platformAutomerge": false, 51 | "labels": ["dependencies", "container-images", "stac"] 52 | }, 53 | { 54 | "description": "Development Seed container images - individual PRs", 55 | "matchManagers": ["helm-values"], 56 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 57 | "matchPackageNames": [ 58 | "/ghcr.io/developmentseed/tipg/", 59 | "/ghcr.io/developmentseed/titiler-md-demo/", 60 | "/ghcr.io/developmentseed/eoapi-k8s-stac-browser/" 61 | ], 62 | "automerge": false, 63 | "platformAutomerge": false, 64 | "labels": ["dependencies", "container-images", "developmentseed"] 65 | }, 66 | { 67 | "description": "Patch updates - no automerge, individual PRs", 68 | "matchManagers": ["helm-values"], 69 | "matchUpdateTypes": ["patch"], 70 | "matchPackageNames": [ 71 | "/ghcr.io/stac-utils/*/", 72 | "/ghcr.io/developmentseed/*/" 73 | ], 74 | "automerge": false, 75 | "platformAutomerge": false, 76 | "labels": ["dependencies", "patch"] 77 | }, 78 | { 79 | "description": "Minor updates - individual PRs", 80 | "matchUpdateTypes": ["minor"], 81 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 82 | "automerge": false, 83 | "platformAutomerge": false, 84 | "labels": ["dependencies", "minor-update"] 85 | }, 86 | { 87 | "description": "Major updates - individual PRs", 88 | "matchUpdateTypes": ["major"], 89 | "schedule": ["after 10pm every weekday", "before 5am every weekday"], 90 | "automerge": false, 91 | "platformAutomerge": false, 92 | "labels": ["dependencies", "major-update", "manual-review"], 93 | "reviewers": ["@maintainers"] 94 | } 95 | ], 96 | "helm-values": { 97 | "managerFilePatterns": ["/charts/.+/values\\.ya?ml$/"] 98 | }, 99 | "helmv3": { 100 | "managerFilePatterns": ["/charts/.+/Chart\\.ya?ml$/"] 101 | }, 102 | "customManagers": [ 103 | { 104 | "customType": "regex", 105 | "description": "Update container image tags in Helm values files", 106 | "managerFilePatterns": ["/charts/.+/values\\.ya?ml$/"], 107 | "matchStrings": [ 108 | "(?ghcr\\.io/[^\\s]+?)\\s*\\n\\s*tag:\\s*[\"']?(?[^\\s\"']+)[\"']?" 109 | ], 110 | "datasourceTemplate": "docker", 111 | "registryUrlTemplate": "https://ghcr.io", 112 | "versioningTemplate": "docker" 113 | } 114 | ], 115 | 116 | "labels": ["dependencies"], 117 | "assignees": [], 118 | "reviewers": [], 119 | "prBodyTemplate": "This PR updates {{depName}} in the eoAPI Kubernetes charts.\n\n**Update details:**\n- **From:** {{currentVersion}}\n- **To:** {{newVersion}}\n- **Type:** {{updateType}} update\n\n**Release notes:**\n{{{changelog}}}\n\n**Testing checklist:**\n- [ ] Verify Helm chart templates render correctly\n- [ ] Test deployment in development environment\n- [ ] Check for any breaking changes in release notes\n- [ ] Validate container image exists and is functional\n\n---\n**Auto-generated by Renovate Bot** 🤖", 120 | "prFooter": "---\n\n**Need help?** Check the [Renovate documentation](https://docs.renovatebot.com/) or [eoAPI documentation](https://eoapi.dev/).", 121 | 122 | "lockFileMaintenance": { 123 | "enabled": true, 124 | "schedule": ["after 10pm on sunday", "before 5am on monday"] 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /charts/eoapi/tests/pgstac_notification_tests.yaml: -------------------------------------------------------------------------------- 1 | suite: pgstac notification triggers tests 2 | templates: 3 | - templates/pgstacbootstrap/configmap.yaml 4 | tests: 5 | - it: "pgstac settings should include notification function when enabled" 6 | set: 7 | pgstacBootstrap.enabled: true 8 | eoapi-notifier.enabled: true 9 | documentIndex: 0 10 | asserts: 11 | - isKind: 12 | of: ConfigMap 13 | - equal: 14 | path: metadata.name 15 | value: RELEASE-NAME-pgstac-settings-config 16 | - matchRegex: 17 | path: data["pgstac-settings.sql"] 18 | pattern: CREATE OR REPLACE FUNCTION notify_items_change_func\(\) 19 | - matchRegex: 20 | path: data["pgstac-settings.sql"] 21 | pattern: RETURNS TRIGGER AS \$\$ 22 | - matchRegex: 23 | path: data["pgstac-settings.sql"] 24 | pattern: PERFORM pg_notify\('pgstac_items_change'::text 25 | 26 | - it: "pgstac settings should include INSERT trigger when enabled" 27 | set: 28 | pgstacBootstrap.enabled: true 29 | eoapi-notifier.enabled: true 30 | documentIndex: 0 31 | asserts: 32 | - isKind: 33 | of: ConfigMap 34 | - matchRegex: 35 | path: data["pgstac-settings.sql"] 36 | pattern: CREATE OR REPLACE TRIGGER notify_items_change_insert 37 | - matchRegex: 38 | path: data["pgstac-settings.sql"] 39 | pattern: AFTER INSERT ON pgstac\.items 40 | - matchRegex: 41 | path: data["pgstac-settings.sql"] 42 | pattern: REFERENCING NEW TABLE AS data 43 | - matchRegex: 44 | path: data["pgstac-settings.sql"] 45 | pattern: FOR EACH STATEMENT EXECUTE FUNCTION notify_items_change_func\(\) 46 | 47 | - it: "pgstac settings should include UPDATE trigger when enabled" 48 | set: 49 | pgstacBootstrap.enabled: true 50 | eoapi-notifier.enabled: true 51 | documentIndex: 0 52 | asserts: 53 | - isKind: 54 | of: ConfigMap 55 | - matchRegex: 56 | path: data["pgstac-settings.sql"] 57 | pattern: CREATE OR REPLACE TRIGGER notify_items_change_update 58 | - matchRegex: 59 | path: data["pgstac-settings.sql"] 60 | pattern: AFTER UPDATE ON pgstac\.items 61 | - matchRegex: 62 | path: data["pgstac-settings.sql"] 63 | pattern: REFERENCING NEW TABLE AS data 64 | 65 | - it: "pgstac settings should include DELETE trigger when enabled" 66 | set: 67 | pgstacBootstrap.enabled: true 68 | eoapi-notifier.enabled: true 69 | documentIndex: 0 70 | asserts: 71 | - isKind: 72 | of: ConfigMap 73 | - matchRegex: 74 | path: data["pgstac-settings.sql"] 75 | pattern: CREATE OR REPLACE TRIGGER notify_items_change_delete 76 | - matchRegex: 77 | path: data["pgstac-settings.sql"] 78 | pattern: AFTER DELETE ON pgstac\.items 79 | - matchRegex: 80 | path: data["pgstac-settings.sql"] 81 | pattern: REFERENCING OLD TABLE AS data 82 | 83 | - it: "notification function should include operation and items in payload when enabled" 84 | set: 85 | pgstacBootstrap.enabled: true 86 | eoapi-notifier.enabled: true 87 | documentIndex: 0 88 | asserts: 89 | - isKind: 90 | of: ConfigMap 91 | - matchRegex: 92 | path: data["pgstac-settings.sql"] 93 | pattern: json_build_object\( 94 | - matchRegex: 95 | path: data["pgstac-settings.sql"] 96 | pattern: "'operation', TG_OP" 97 | - matchRegex: 98 | path: data["pgstac-settings.sql"] 99 | pattern: "'items', jsonb_agg\\(" 100 | - matchRegex: 101 | path: data["pgstac-settings.sql"] 102 | pattern: "'collection', data\\.collection" 103 | - matchRegex: 104 | path: data["pgstac-settings.sql"] 105 | pattern: "'id', data\\.id" 106 | 107 | - it: "notification triggers should not be included by default (disabled by default)" 108 | set: 109 | pgstacBootstrap.enabled: true 110 | documentIndex: 0 111 | asserts: 112 | - isKind: 113 | of: ConfigMap 114 | - equal: 115 | path: metadata.name 116 | value: RELEASE-NAME-pgstac-settings-config 117 | - notMatchRegex: 118 | path: data["pgstac-settings.sql"] 119 | pattern: CREATE OR REPLACE FUNCTION notify_items_change_func\(\) 120 | 121 | - it: "notification triggers should not be included when disabled" 122 | set: 123 | pgstacBootstrap.enabled: true 124 | eoapi-notifier.enabled: false 125 | documentIndex: 0 126 | asserts: 127 | - isKind: 128 | of: ConfigMap 129 | - equal: 130 | path: metadata.name 131 | value: RELEASE-NAME-pgstac-settings-config 132 | - notMatchRegex: 133 | path: data["pgstac-settings.sql"] 134 | pattern: CREATE OR REPLACE FUNCTION notify_items_change_func\(\) 135 | - notMatchRegex: 136 | path: data["pgstac-settings.sql"] 137 | pattern: notify_items_change_insert 138 | - notMatchRegex: 139 | path: data["pgstac-settings.sql"] 140 | pattern: notify_items_change_update 141 | - notMatchRegex: 142 | path: data["pgstac-settings.sql"] 143 | pattern: notify_items_change_delete 144 | 145 | - it: "pgstac settings configmap should not be created when pgstacBootstrap is disabled" 146 | set: 147 | pgstacBootstrap.enabled: false 148 | asserts: 149 | - hasDocuments: 150 | count: 1 151 | --------------------------------------------------------------------------------