Skip to content

Commit

Permalink
Add fastapi-safir-uws starter
Browse files Browse the repository at this point in the history
Add a starter for vo-cutouts-style FastAPI Safir apps that use UWS.
Update the code underlying phalanx application create so that it will
substitute <CHARTENVPREFIX> in every template, not just
configmap.yaml.
  • Loading branch information
rra committed Jul 30, 2024
1 parent 12e0666 commit 07314c9
Show file tree
Hide file tree
Showing 19 changed files with 740 additions and 14 deletions.
8 changes: 7 additions & 1 deletion docs/developers/helm-chart/create-new-chart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ Use the ``--starter`` flag to specify a different Helm chart starter.
There are three options:

fastapi-safir
Use this starter for FastAPI web services based on Safir, created from the "FastAPI application (Safir)" template.
Use this starter for FastAPI web services based on Safir, created from the "FastAPI application (Safir)" template with the "Default" flavor selected.
This is the default.

fastapi-safir-uws
Use this starter for FastAPI web services based on Safir that use UWS.
These services separate the work of the service into a frontend and several backend workers, connected by a queuing system.
It is used for services that have a backend that needs to run on a stack container.
This starter corresponds to applications created from the "FastAPI application (Safir)" template with the "UWS" flavor selected.

web-service
Use this starter if the new Helm application is some other web service.

Expand Down
1 change: 1 addition & 0 deletions src/phalanx/models/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ class HelmStarter(Enum):
EMPTY = "empty"
WEB_SERVICE = "web-service"
FASTAPI_SAFIR = "fastapi-safir"
FASTAPI_SAFIR_UWS = "fastapi-safir-uws"
15 changes: 7 additions & 8 deletions src/phalanx/storage/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,13 @@ def create(
with (path / "Chart.yaml").open("w") as fh:
yaml.dump(chart, fh)

# Support an additional substitution variable, <CHARTENVPREFIX>,
# that's only used in templates/configmap.yaml.
configmap_path = path / "templates" / "configmap.yaml"
if configmap_path.exists():
configmap = configmap_path.read_text().replace(
"<CHARTENVPREFIX>", application.upper().replace("-", "_")
)
configmap_path.write_text(configmap)
# Support an additional substitution variable, <CHARTENVPREFIX>.
for env_path in (path / "templates").iterdir():
if env_path.is_file() and env_path.suffix == ".yaml":
text = env_path.read_text().replace(
"<CHARTENVPREFIX>", application.upper().replace("-", "_")
)
env_path.write_text(text)

def dependency_update(
self, application: str, *, quiet: bool = False
Expand Down
9 changes: 4 additions & 5 deletions starters/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Helm starters for Phalanx

Each subdirectory of this directory is a Helm starter for a class of Phalanx service.
Use the starters with the `-p` option to `helm create`.
For example, from the `applications` directory:
These are used by the `phalanx application create` command.

For example, from the top of a Phalanx checkout:

```sh
helm create new-service -p $(pwd)/../starters/rsp-web-service
phalanx application create --starter fastapi-safir <application>
```

The path to the starter directory must be absolute, not relative, or Helm will try to use it has a path relative to `$HOME/.local/share/helm`.
12 changes: 12 additions & 0 deletions starters/fastapi-safir-uws/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v2
name: <CHARTNAME>
version: 1.0.0
description: "Image cutout service complying with IVOA SODA"
sources:
- "https://github.com/lsst-sqre/<CHARTNAME>"
appVersion: 0.1.0

dependencies:
- name: redis
version: 1.0.12
repository: https://lsst-sqre.github.io/charts/
13 changes: 13 additions & 0 deletions starters/fastapi-safir-uws/secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
database-password:
description: >-
Password used to authenticate to the PostgreSQL database used to store job
information. This password may be changed at any time.
redis-password:
description: >-
Password used to authenticate to the internal Redis server, deployed as
part of the same Argo CD application and used to manage the work
queue. This secret can be changed at any time, but both the Redis server
and all deployments will then have to be restarted to pick up the new
value.
generate:
type: password
27 changes: 27 additions & 0 deletions starters/fastapi-safir-uws/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "<CHARTNAME>.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

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

{{/*
Selector labels
*/}}
{{- define "<CHARTNAME>.selectorLabels" -}}
app.kubernetes.io/name: "<CHARTNAME>"
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
20 changes: 20 additions & 0 deletions starters/fastapi-safir-uws/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: <CHARTNAME>
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
data:
<CHARTENVPREFIX>_ARQ_QUEUE_URL: "redis://<CHARTNAME>-redis.{{ .Release.Namespace }}"
{{- if .Values.cloudsql.enabled }}
<CHARTENVPREFIX>_DATABASE_URL: "postgresql://<CHARTNAME>@localhost/<CHARTNAME>"
{{- end }}
<CHARTENVPREFIX>_GRACE_PERIOD: {{ .Values.config.gracePeriod | quote }}
<CHARTENVPREFIX>_LIFETIME: {{ .Values.config.lifetime | quote }}
<CHARTENVPREFIX>_SERVICE_ACCOUNT: {{ required "config.serviceAccount must be set" .Values.config.serviceAccount | quote }}
<CHARTENVPREFIX>_STORAGE_URL: {{ required "config.storageBucketUrl must be set" .Values.config.storageBucketUrl | quote }}
<CHARTENVPREFIX>_SYNC_TIMEOUT: {{ .Values.config.syncTimeout | quote }}
<CHARTENVPREFIX>_TIMEOUT: {{ .Values.config.timeout | quote }}
<CHARTENVPREFIX>_LOG_LEVEL: {{ .Values.config.loglevel | quote }}
<CHARTENVPREFIX>_PATH_PREFIX: {{ .Values.config.pathPrefix | quote }}
<CHARTENVPREFIX>_PROFILE: {{ .Values.config.logProfile | quote }}
96 changes: 96 additions & 0 deletions starters/fastapi-safir-uws/templates/db-worker-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: <CHARTNAME>-db-worker
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.databaseWorker.replicaCount }}
selector:
matchLabels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: "db-worker"
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.databaseWorker.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: "db-worker"
<CHARTNAME>-redis-client: "true"
spec:
{{- with .Values.databaseWorker.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.cloudsql.enabled }}
serviceAccountName: "<CHARTNAME>"
{{- else }}
automountServiceAccountToken: false
{{- end }}
containers:
{{- if .Values.cloudsql.enabled }}
- name: "cloud-sql-proxy"
command:
- "/cloud_sql_proxy"
- "-ip_address_types=PRIVATE"
- "-log_debug_stdout=true"
- "-structured_logs=true"
- "-instances={{ required "cloudsql.instanceConnectionName must be specified" .Values.cloudsql.instanceConnectionName }}=tcp:5432"
image: "{{ .Values.cloudsql.image.repository }}:{{ .Values.cloudsql.image.tag }}"
imagePullPolicy: {{ .Values.cloudsql.image.pullPolicy | quote }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "all"
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
{{- end }}
- name: "db-worker"
command:
- "arq"
- "<CHARTNAME>.workers.uws.WorkerSettings"
env:
- name: "<CHARTENVPREFIX>_ARQ_QUEUE_PASSWORD"
valueFrom:
secretKeyRef:
name: "<CHARTNAME>"
key: "redis-password"
- name: "<CHARTENVPREFIX>_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "<CHARTNAME>"
key: "database-password"
envFrom:
- configMapRef:
name: "<CHARTNAME>"
{{- with .Values.databaseWorker.resources }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "all"
readOnlyRootFilesystem: true
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
{{- with .Values.databaseWorker.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.databaseWorker.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
15 changes: 15 additions & 0 deletions starters/fastapi-safir-uws/templates/db-worker-networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: <CHARTNAME>-db-worker
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
podSelector:
# This policy controls inbound access to the database workers.
matchLabels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: "db-worker"
policyTypes:
# Block all inbound access.
- Ingress
105 changes: 105 additions & 0 deletions starters/fastapi-safir-uws/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: "<CHARTNAME>"
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: "frontend"
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.frontend.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: "frontend"
<CHARTNAME>-redis-client: "true"
spec:
{{- with .Values.frontend.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.cloudsql.enabled }}
serviceAccountName: "<CHARTNAME>"
{{- else }}
automountServiceAccountToken: false
{{- end }}
containers:
{{- if .Values.cloudsql.enabled }}
- name: "cloud-sql-proxy"
command:
- "/cloud_sql_proxy"
- "-ip_address_types=PRIVATE"
- "-log_debug_stdout=true"
- "-structured_logs=true"
- "-instances={{ required "cloudsql.instanceConnectionName must be specified" .Values.cloudsql.instanceConnectionName }}=tcp:5432"
image: "{{ .Values.cloudsql.image.repository }}:{{ .Values.cloudsql.image.tag }}"
imagePullPolicy: {{ .Values.cloudsql.image.pullPolicy | quote }}
{{- with .Values.cloudsql.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "all"
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
{{- end }}
- name: "<CHARTNAME>"
env:
- name: "<CHARTENVPREFIX>_ARQ_QUEUE_PASSWORD"
valueFrom:
secretKeyRef:
name: "<CHARTNAME>"
key: "redis-password"
- name: "<CHARTENVPREFIX>_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "<CHARTNAME>"
key: "database-password"
envFrom:
- configMapRef:
name: "<CHARTNAME>"
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
ports:
- containerPort: 8080
name: "http"
protocol: "TCP"
readinessProbe:
httpGet:
path: "/api/<CHARTNAME>/availability"
port: "http"
{{- with .Values.frontend.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "all"
readOnlyRootFilesystem: true
{{- with .Values.frontend.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
{{- with .Values.frontend.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
37 changes: 37 additions & 0 deletions starters/fastapi-safir-uws/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: gafaelfawr.lsst.io/v1alpha1
kind: GafaelfawrIngress
metadata:
name: "<CHARTNAME>"
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
config:
baseUrl: {{ .Values.global.baseUrl | quote }}
scopes:
all:
- "read:image"
# Request a delegated token to use for making calls to Butler server with the
# end-user's credentials.
delegate:
internal:
service: "<CHARTNAME>"
scopes:
- "read:image"
template:
metadata:
name: "<CHARTNAME>"
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 6 }}
{{- end }}
spec:
rules:
- host: {{ required "global.host must be set" .Values.global.host | quote }}
http:
paths:
- path: {{ .Values.config.pathPrefix | quote }}
pathType: "Prefix"
backend:
service:
name: "<CHARTNAME>"
port:
number: 8080
Loading

0 comments on commit 07314c9

Please sign in to comment.