From 6d4a9b77b1b2207bbacb28300fbaf5b20cdf3246 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Fri, 12 Dec 2025 14:09:38 +0200 Subject: [PATCH 01/11] Add cos-common lib, migrate angular-osf and osf-graveyvalet --- .gitignore | 4 +- angular-osf/Chart.yaml | 17 +- angular-osf/files/nginx.conf | 320 +++ angular-osf/templates/NOTES.txt | 43 +- angular-osf/templates/_helpers.tpl | 43 - angular-osf/templates/configmap.yaml | 340 ---- angular-osf/templates/deployment.yaml | 120 -- angular-osf/templates/hpa.yaml | 19 - angular-osf/templates/ingress.yaml | 111 -- angular-osf/templates/main.yaml | 10 + angular-osf/templates/pdb.yaml | 21 - angular-osf/templates/service.yaml | 19 - angular-osf/values.yaml | 506 +++-- cos-common/Chart.yaml | 6 + cos-common/README.md | 159 ++ cos-common/templates/_certificate.tpl | 136 ++ cos-common/templates/_configmap.tpl | 93 + cos-common/templates/_cronjob.tpl | 55 + cos-common/templates/_deployment.tpl | 44 + cos-common/templates/_helpers.tpl | 988 ++++++++++ cos-common/templates/_hpa.tpl | 41 + cos-common/templates/_ingress.tpl | 210 ++ cos-common/templates/_job.tpl | 18 + cos-common/templates/_networkpolicy.tpl | 112 ++ cos-common/templates/_pdb.tpl | 31 + cos-common/templates/_pvc.tpl | 111 ++ cos-common/templates/_secret.tpl | 130 ++ cos-common/templates/_service.tpl | 80 + cos-common/templates/_statefulset.tpl | 55 + cos-common/values.schema.json | 1711 +++++++++++++++++ osf-graveyvalet/Chart.yaml | 15 +- osf-graveyvalet/files/nginx.conf | 122 ++ osf-graveyvalet/templates/NOTES.txt | 43 +- osf-graveyvalet/templates/_helpers.tpl | 163 -- .../templates/beat-deployment.yaml | 133 -- osf-graveyvalet/templates/beat-pvc.yaml | 25 - osf-graveyvalet/templates/beat.yaml | 6 + .../templates/certificate-networkpolicy.yaml | 20 - osf-graveyvalet/templates/certificate.yaml | 34 - osf-graveyvalet/templates/configmap.yaml | 143 -- osf-graveyvalet/templates/deployment.yaml | 173 -- osf-graveyvalet/templates/hpa.yaml | 20 - osf-graveyvalet/templates/ingress.yaml | 49 - osf-graveyvalet/templates/main.yaml | 10 + osf-graveyvalet/templates/migration-job.yaml | 59 - .../templates/migration-secret.yaml | 30 - osf-graveyvalet/templates/migration.yaml | 3 + osf-graveyvalet/templates/networkpolicy.yaml | 30 - osf-graveyvalet/templates/pdb.yaml | 21 - osf-graveyvalet/templates/secret.yaml | 26 - osf-graveyvalet/templates/service.yaml | 19 - .../templates/worker-deployment.yaml | 91 - osf-graveyvalet/templates/worker.yaml | 6 + osf-graveyvalet/values.yaml | 1007 +++++++--- 54 files changed, 5702 insertions(+), 2099 deletions(-) create mode 100644 angular-osf/files/nginx.conf delete mode 100644 angular-osf/templates/_helpers.tpl delete mode 100644 angular-osf/templates/configmap.yaml delete mode 100644 angular-osf/templates/deployment.yaml delete mode 100644 angular-osf/templates/hpa.yaml delete mode 100644 angular-osf/templates/ingress.yaml create mode 100644 angular-osf/templates/main.yaml delete mode 100644 angular-osf/templates/pdb.yaml delete mode 100644 angular-osf/templates/service.yaml create mode 100644 cos-common/Chart.yaml create mode 100644 cos-common/README.md create mode 100644 cos-common/templates/_certificate.tpl create mode 100644 cos-common/templates/_configmap.tpl create mode 100644 cos-common/templates/_cronjob.tpl create mode 100644 cos-common/templates/_deployment.tpl create mode 100644 cos-common/templates/_helpers.tpl create mode 100644 cos-common/templates/_hpa.tpl create mode 100644 cos-common/templates/_ingress.tpl create mode 100644 cos-common/templates/_job.tpl create mode 100644 cos-common/templates/_networkpolicy.tpl create mode 100644 cos-common/templates/_pdb.tpl create mode 100644 cos-common/templates/_pvc.tpl create mode 100644 cos-common/templates/_secret.tpl create mode 100644 cos-common/templates/_service.tpl create mode 100644 cos-common/templates/_statefulset.tpl create mode 100644 cos-common/values.schema.json create mode 100644 osf-graveyvalet/files/nginx.conf delete mode 100644 osf-graveyvalet/templates/_helpers.tpl delete mode 100644 osf-graveyvalet/templates/beat-deployment.yaml delete mode 100644 osf-graveyvalet/templates/beat-pvc.yaml create mode 100644 osf-graveyvalet/templates/beat.yaml delete mode 100644 osf-graveyvalet/templates/certificate-networkpolicy.yaml delete mode 100644 osf-graveyvalet/templates/certificate.yaml delete mode 100644 osf-graveyvalet/templates/configmap.yaml delete mode 100644 osf-graveyvalet/templates/deployment.yaml delete mode 100644 osf-graveyvalet/templates/hpa.yaml delete mode 100644 osf-graveyvalet/templates/ingress.yaml create mode 100644 osf-graveyvalet/templates/main.yaml delete mode 100644 osf-graveyvalet/templates/migration-job.yaml delete mode 100644 osf-graveyvalet/templates/migration-secret.yaml create mode 100644 osf-graveyvalet/templates/migration.yaml delete mode 100644 osf-graveyvalet/templates/networkpolicy.yaml delete mode 100644 osf-graveyvalet/templates/pdb.yaml delete mode 100644 osf-graveyvalet/templates/secret.yaml delete mode 100644 osf-graveyvalet/templates/service.yaml delete mode 100644 osf-graveyvalet/templates/worker-deployment.yaml create mode 100644 osf-graveyvalet/templates/worker.yaml diff --git a/.gitignore b/.gitignore index 711a39c5..41017a68 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -charts/ \ No newline at end of file +charts/ +**/stage-4.yaml +**/Chart.lock \ No newline at end of file diff --git a/angular-osf/Chart.yaml b/angular-osf/Chart.yaml index c368382a..c1127627 100644 --- a/angular-osf/Chart.yaml +++ b/angular-osf/Chart.yaml @@ -1,9 +1,18 @@ -apiVersion: v1 -description: An Angular application for the Open Science Framework +apiVersion: v2 name: angular-osf -version: 0.0.4 +description: Angular OSF application +type: application +version: 1.0.0 +appVersion: "0.0.5" keywords: - angular +dependencies: + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ maintainers: - name: Matt Frazier email: matt@cos.io @@ -11,5 +20,3 @@ maintainers: - name: Uditi Mehta email: uditi@cos.io url: https://github.com/uditijmehta -engine: gotpl -tillerVersion: '>=2.7.0' diff --git a/angular-osf/files/nginx.conf b/angular-osf/files/nginx.conf new file mode 100644 index 00000000..2add350d --- /dev/null +++ b/angular-osf/files/nginx.conf @@ -0,0 +1,320 @@ +user nginx; +worker_processes 1; + +load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; +{{- if .Values.main.nginx.vts.enabled }} +load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; +load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; +{{- end }} +{{- range .Values.main.nginx.modules }} +load_module {{ . }}; +{{- end }} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"' + ' Proxy: "$proxy_host" "$upstream_addr"' + ' URI: "$uri"' + ' Prerender: "$prerender"'; + access_log /var/log/nginx/access.log main; + + real_ip_header {{ .Values.main.nginx.realIpHeader }}; + real_ip_recursive {{ .Values.main.nginx.realIpRecursive }}; + {{- range .Values.main.nginx.proxySourceRanges }} + set_real_ip_from {{ . }}; + {{- end }} + + {{- if .Values.main.nginx.vts.enabled }} + geoip_country /etc/nginx/GeoIP.dat; + geoip_city /etc/nginx/GeoLiteCity.dat; + geoip_proxy_recursive on; + {{- range .Values.main.nginx.proxySourceRanges }} + geoip_proxy {{ . }}; + {{- end }} + + vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.main.nginx.vts.statusZoneSize }}; + vhost_traffic_status_filter_by_set_key {{ .Values.main.nginx.vts.defaultFilterKey }}; + {{- end }} + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 620s; + types_hash_max_size 2048; +# sendfile_max_chunk 512; + server_tokens off; + + gzip on; + gzip_disable "MSIE [1-6]\.(?!.*SV1)"; + gzip_comp_level 2; + gzip_min_length 512; + gzip_proxied any; + gzip_vary on; + gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml application/xml text/javascript application/json application/xml+rss application/vnd.api+json; + + brotli on; + brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml application/xml text/javascript application/json application/xml+rss application/vnd.api+json; + + {{- if .Values.main.nginx.vts.enabled }} + server { + listen {{ .Values.main.nginx.vts.internalPort }}; + server_name _; + + location /healthz { + access_log off; + return 200; + } + + location /nginx_status { + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + } + {{- end }} + + + {{- if .Values.main.nginx.brandedSubdomains }} + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + server_name "~^(?({{ join "|" .Values.main.nginx.brandedSubdomains }}))\.{{ .Values.main.nginx.primaryDomain | replace "." "\\." }}$"; + {{- if .Values.main.prerender.enabled }} + set $prerender 0; + + if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|googlebot|google-inspectiontool|archive.org_bot|pingbot") { + set $prerender 1; + } + + # Google translate + if ($http_referer ~* "translate\.googleusercontent\.com") { + set $prerender 1; + } + + if ($args ~* "_escaped_fragment_") { + set $prerender 1; + } + + if ($http_user_agent ~* "prerender") { + set $prerender 0; + } + + if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff)") { + set $prerender 0; + } + + # Exclude download links from prerender + if ($uri ~* ^/download.*) { + set $prerender 0; + } + + if ($arg_action ~* "download") { + set $prerender 0; + } + + if ($uri ~* ^/\w+/download(/?$|/.*)) { + set $prerender 0; + } + + if ($uri ~* ^/preprints/(\w+/download|\w+/\w+/download)(/?$|/.*)) { + set $prerender 0; + } + {{- end }} + + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /favicon.ico { + # TODO: determine real favicon location + alias /static/assets/images/favicon.ico; + } + + location = /robots.txt { + alias /static/robots.txt; + } + + location / { + {{- if .Values.main.prerender.enabled }} + if ($prerender = 1) { + rewrite .* /https://$host$request_uri? break; + proxy_pass http://{{ .Values.main.prerender.service.name }}:{{ .Values.main.prerender.service.externalPort }}; + } + {{- end }} + + if ($request_uri ~* "^/\w{5}(/.*)?/?$") { + return 307 https://{{ .Values.main.nginx.primaryDomain }}$request_uri; + } + return 307 https://{{ .Values.main.nginx.primaryDomain }}/registries/$sub$request_uri; + } + + } + {{- end }} + + {{- if .Values.main.nginx.preprintDomainMap }} + {{- range $key, $val := .Values.main.nginx.preprintDomainMap }} + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + server_name {{ $key }}; + return 301 https://{{ $.Values.main.nginx.primaryDomain }}/preprints/{{ $val }}$request_uri; + } + {{- end }} + {{- end }} + + {{- if .Values.main.nginx.institutionDomainMap }} + {{- range $key, $val := .Values.main.nginx.institutionDomainMap }} + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + server_name {{ $key }}; + return 301 https://{{ $.Values.main.nginx.primaryDomain }}/institutions/{{ $val }}$request_uri; + } + {{- end }} + {{- end }} + + server { + listen {{ .Values.main.http.containers.nginx.internalPort }} default_server; + server_name _; + + client_max_body_size 25M; + keepalive_timeout 620s; + + root /static; + index index.html; + {{- if .Values.main.prerender.enabled }} + set $prerender 0; + + if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|googlebot|google-inspectiontool|archive.org_bot|pingbot") { + set $prerender 1; + } + + # Google translate + if ($http_referer ~* "translate\.googleusercontent\.com") { + set $prerender 1; + } + + if ($args ~* "_escaped_fragment_") { + set $prerender 1; + } + + if ($http_user_agent ~* "prerender") { + set $prerender 0; + } + + if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff)") { + set $prerender 0; + } + + # Exclude download links from prerender + if ($uri ~* ^/download.*) { + set $prerender 0; + } + + if ($arg_action ~* "download") { + set $prerender 0; + } + + if ($uri ~* ^/\w+/download(/?$|/.*)) { + set $prerender 0; + } + + if ($uri ~* ^/preprints/(\w+/download|\w+/\w+/download)(/?$|/.*)) { + set $prerender 0; + } + {{- end }} + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /healthz { + access_log off; + return 200; + } + + location = /robots.txt { + alias /static/robots.txt; + } + + location = /favicon.ico { + # TODO: determine real favicon location + alias /static/assets/images/favicon.ico; + } + + + {{- if (index .Values.main.nginx "additionalConfig") }} + {{- .Values.main.nginx.additionalConfig | nindent 10 }} + {{- end }} + + include /etc/nginx/conf.d/*.conf; + + location ~* ^/share(/?$|/.*) { + return 301 {{ .Values.main.share.url }}; + } + + location / { + {{- if .Values.main.prerender.enabled }} + if ($prerender = 1) { + rewrite .* /https://$host$request_uri? break; + proxy_pass http://{{ .Values.main.prerender.service.name }}:{{ .Values.main.prerender.service.externalPort }}; + } + {{- end }} + + # Disable caching of application requests + #add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; + #add_header Expires "-1"; + #add_header Pragma "no-cache"; + + # Don't cache index.html and main bundle files + location ~* (?:index\.html|main.*\.js|\.json)$ { + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + } + + # Cache static assets + location ~* \.(?:jpg|jpeg|gif|png|ico|svg|woff|woff2|ttf|eot|css|js)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + + # URL rewrites + rewrite "^/project/.*?/node/(.*)" https://$host/$1 permanent; + rewrite "^/project/([a-zA-Z0-9]{5,}.*)" https://$host/$1 permanent; + rewrite "^/profile/([a-zA-Z0-9]{5,})" https://$host/$1 permanent; + rewrite "^/([a-zA-Z0-9]{5})/download(/.*)?" https://$host/download/$1 permanent; + {{- range .Values.main.nginx.additionalRewrites }} + {{ . }} + {{- end }} + if ($args ~* "(.*)action=download(.*)") { + set $args "$1$2"; + rewrite "^/([a-zA-Z0-9]{5})(/.*)?" https://$host/download/$1 permanent; + } + + try_files $uri $uri/ /index.html; + } + } + + {{- if .Values.main.nginx.redirects.enabled }} + # WARNING: Must remain at the bottom to ensure connections default to + # the first server configuration for institutions + {{- range $value := .Values.main.nginx.redirects.domains }} + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + server_name {{ $value.from | join " " }}; + return 301 https://{{ $value.to }}$request_uri; + } + {{- end }} + {{- end }} +} \ No newline at end of file diff --git a/angular-osf/templates/NOTES.txt b/angular-osf/templates/NOTES.txt index 560b4164..32e3215c 100644 --- a/angular-osf/templates/NOTES.txt +++ b/angular-osf/templates/NOTES.txt @@ -1,17 +1,28 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "angular.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "angular.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "angular.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "angular.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +Component fullname: {{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} + +{{- if and .Values.main.ingress.enabled (.Values.main.ingress.hosts) }} +Ingress hosts: +{{- $hosts := list }} +{{- if and (kindIs "map" .Values.main.ingress.hosts) (or (hasKey .Values.main.ingress.hosts "primary") (hasKey .Values.main.ingress.hosts "secondary")) }} + {{- range $h := (default (list) .Values.main.ingress.hosts.primary) }} + {{- $hosts = append $hosts $h }} + {{- end }} + {{- range $h := (default (list) .Values.main.ingress.hosts.secondary) }} + {{- $hosts = append $hosts $h }} + {{- end }} +{{- else }} + {{- range $h := .Values.main.ingress.hosts }} + {{- $hosts = append $hosts $h.host }} + {{- end }} +{{- end }} +{{- range $hosts }} + - {{ . }} +{{- end }} +{{- else }} +Service ports: +{{- range .Values.main.service.ports }} + - {{ .name }}: {{ .port }} +{{- end }} +Port-forward example: +kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} 8080:{{ (index .Values.main.service.ports 0).port }} {{- end }} diff --git a/angular-osf/templates/_helpers.tpl b/angular-osf/templates/_helpers.tpl deleted file mode 100644 index 6cb131a9..00000000 --- a/angular-osf/templates/_helpers.tpl +++ /dev/null @@ -1,43 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "angular.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "angular.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "angular.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Overridable deployment annotations -*/}} -{{- define "angular.deploymentAnnotations" -}} -checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -{{- end -}} - -{{- define "angular.environment" -}} -{{- $fullname := include "angular.fullname" . -}} -{{- range $key := keys .Values.configEnvs }} -- name: {{ $key }} - valueFrom: - configMapKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end -}} -{{- end -}} diff --git a/angular-osf/templates/configmap.yaml b/angular-osf/templates/configmap.yaml deleted file mode 100644 index 3503dd2c..00000000 --- a/angular-osf/templates/configmap.yaml +++ /dev/null @@ -1,340 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "angular.fullname" . }} - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: -{{- define "angular.inlineconfigs" }} -nginx.conf: |- - user nginx; - worker_processes 1; - - load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; - {{- if .Values.nginx.vts.enabled }} - load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; - load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; - {{- end }} - {{- range .Values.nginx.modules }} - load_module {{ . }}; - {{- end }} - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"' - ' Proxy: "$proxy_host" "$upstream_addr"' - ' URI: "$uri"' - ' Prerender: "$prerender"'; - access_log /var/log/nginx/access.log main; - - real_ip_header {{ .Values.nginx.realIpHeader }}; - real_ip_recursive {{ .Values.nginx.realIpRecursive }}; - {{- range .Values.nginx.proxySourceRanges }} - set_real_ip_from {{ . }}; - {{- end }} - - {{- if .Values.nginx.vts.enabled }} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - {{- range .Values.nginx.proxySourceRanges }} - geoip_proxy {{ . }}; - {{- end }} - - vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; - {{- end }} - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 620s; - types_hash_max_size 2048; - # sendfile_max_chunk 512; - server_tokens off; - - gzip on; - gzip_disable "MSIE [1-6]\.(?!.*SV1)"; - gzip_comp_level 2; - gzip_min_length 512; - gzip_proxied any; - gzip_vary on; - gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml application/xml text/javascript application/json application/xml+rss application/vnd.api+json; - - brotli on; - brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml application/xml text/javascript application/json application/xml+rss application/vnd.api+json; - - {{- if .Values.nginx.vts.enabled }} - server { - listen {{ .Values.nginx.vts.internalPort }}; - server_name _; - - location /healthz { - access_log off; - return 200; - } - - location /nginx_status { - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - } - } - {{- end }} - - - {{- if .Values.nginx.brandedSubdomains }} - server { - listen {{ .Values.service.internalPort }}; - server_name "~^(?({{ join "|" .Values.nginx.brandedSubdomains }}))\.{{ .Values.nginx.primaryDomain | replace "." "\\." }}$"; - {{- if .Values.prerender.enabled }} - set $prerender 0; - - if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|googlebot|google-inspectiontool|archive.org_bot|pingbot") { - set $prerender 1; - } - - # Google translate - if ($http_referer ~* "translate\.googleusercontent\.com") { - set $prerender 1; - } - - if ($args ~* "_escaped_fragment_") { - set $prerender 1; - } - - if ($http_user_agent ~* "prerender") { - set $prerender 0; - } - - if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff)") { - set $prerender 0; - } - - # Exclude download links from prerender - if ($uri ~* ^/download.*) { - set $prerender 0; - } - - if ($arg_action ~* "download") { - set $prerender 0; - } - - if ($uri ~* ^/\w+/download(/?$|/.*)) { - set $prerender 0; - } - - if ($uri ~* ^/preprints/(\w+/download|\w+/\w+/download)(/?$|/.*)) { - set $prerender 0; - } - {{- end }} - - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /favicon.ico { - # TODO: determine real favicon location - alias /static/assets/images/favicon.ico; - } - - location = /robots.txt { - alias /static/robots.txt; - } - - location / { - {{- if .Values.prerender.enabled }} - if ($prerender = 1) { - rewrite .* /https://$host$request_uri? break; - proxy_pass http://{{ .Values.prerender.service.name }}:{{ .Values.prerender.service.externalPort }}; - } - {{- end }} - - if ($request_uri ~* "^/\w{5}(/.*)?/?$") { - return 307 https://{{ .Values.nginx.primaryDomain }}$request_uri; - } - return 307 https://{{ .Values.nginx.primaryDomain }}/registries/$sub$request_uri; - } - - } - {{- end }} - - {{- if .Values.nginx.preprintDomainMap }} - {{- range $key, $val := .Values.nginx.preprintDomainMap }} - server { - listen {{ $.Values.service.internalPort }}; - server_name {{ $key }}; - return 301 https://{{ $.Values.nginx.primaryDomain }}/preprints/{{ $val }}$request_uri; - } - {{- end }} - {{- end }} - - {{- if .Values.nginx.institutionDomainMap }} - {{- range $key, $val := .Values.nginx.institutionDomainMap }} - server { - listen {{ $.Values.service.internalPort }}; - server_name {{ $key }}; - return 301 https://{{ $.Values.nginx.primaryDomain }}/institutions/{{ $val }}$request_uri; - } - {{- end }} - {{- end }} - - server { - listen {{ .Values.service.internalPort }} default_server; - server_name _; - - client_max_body_size 25M; - keepalive_timeout 620s; - - root /static; - index index.html; - {{- if .Values.prerender.enabled }} - set $prerender 0; - - if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|googlebot|google-inspectiontool|archive.org_bot|pingbot") { - set $prerender 1; - } - - # Google translate - if ($http_referer ~* "translate\.googleusercontent\.com") { - set $prerender 1; - } - - if ($args ~* "_escaped_fragment_") { - set $prerender 1; - } - - if ($http_user_agent ~* "prerender") { - set $prerender 0; - } - - if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff)") { - set $prerender 0; - } - - # Exclude download links from prerender - if ($uri ~* ^/download.*) { - set $prerender 0; - } - - if ($arg_action ~* "download") { - set $prerender 0; - } - - if ($uri ~* ^/\w+/download(/?$|/.*)) { - set $prerender 0; - } - - if ($uri ~* ^/preprints/(\w+/download|\w+/\w+/download)(/?$|/.*)) { - set $prerender 0; - } - {{- end }} - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /healthz { - access_log off; - return 200; - } - - location = /robots.txt { - alias /static/robots.txt; - } - - location = /favicon.ico { - # TODO: determine real favicon location - alias /static/assets/images/favicon.ico; - } - - - {{- if (index .Values.nginx "additionalConfig") }} - {{- .Values.nginx.additionalConfig | nindent 10 }} - {{- end }} - - include /etc/nginx/conf.d/*.conf; - - location ~* ^/share(/?$|/.*) { - return 301 {{ .Values.share.url }}; - } - - location / { - {{- if .Values.prerender.enabled }} - if ($prerender = 1) { - rewrite .* /https://$host$request_uri? break; - proxy_pass http://{{ .Values.prerender.service.name }}:{{ .Values.prerender.service.externalPort }}; - } - {{- end }} - - # Disable caching of application requests - #add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; - #add_header Expires "-1"; - #add_header Pragma "no-cache"; - - # Don't cache index.html and main bundle files - location ~* (?:index\.html|main.*\.js|\.json)$ { - expires -1; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; - } - - # Cache static assets - location ~* \.(?:jpg|jpeg|gif|png|ico|svg|woff|woff2|ttf|eot|css|js)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - access_log off; - } - - - # URL rewrites - rewrite "^/project/.*?/node/(.*)" https://$host/$1 permanent; - rewrite "^/project/([a-zA-Z0-9]{5,}.*)" https://$host/$1 permanent; - rewrite "^/profile/([a-zA-Z0-9]{5,})" https://$host/$1 permanent; - rewrite "^/([a-zA-Z0-9]{5})/download(/.*)?" https://$host/download/$1 permanent; - {{- range .Values.nginx.additionalRewrites }} - {{ . }} - {{- end }} - if ($args ~* "(.*)action=download(.*)") { - set $args "$1$2"; - rewrite "^/([a-zA-Z0-9]{5})(/.*)?" https://$host/download/$1 permanent; - } - - try_files $uri $uri/ /index.html; - } - } - - {{- if .Values.nginx.redirects.enabled }} - # WARNING: Must remain at the bottom to ensure connections default to - # the first server configuration for institutions - {{- range $value := .Values.nginx.redirects.domains }} - server { - listen {{ $.Values.service.internalPort }}; - server_name {{ $value.from | join " " }}; - return 301 https://{{ $value.to }}$request_uri; - } - {{- end }} - {{- end }} - } -{{- end -}} -{{- range $key, $value := .Values.configEnvs }} - {{ $key }}: {{ $value | quote }} -{{- end }} -{{- range $key, $value := merge .Values.configFiles (include "angular.inlineconfigs" . | fromYaml) ((.Files.Glob "files/*").AsConfig | fromYaml) }} - {{ $key }}: |- - {{- $value | nindent 4 }} -{{- end }} diff --git a/angular-osf/templates/deployment.yaml b/angular-osf/templates/deployment.yaml deleted file mode 100644 index 5d15d0a8..00000000 --- a/angular-osf/templates/deployment.yaml +++ /dev/null @@ -1,120 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "angular.fullname" . }} - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} - replicas: {{ .Values.replicaCount }} - template: - metadata: - labels: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} - annotations: - {{- include "angular.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} - {{- end }} - initContainers: - - name: {{ .Values.name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - set -e - cp -Rf /code/dist/osf/browser/* /static/ - {{ if hasKey .Values.configFiles "config.json" -}} - cp -Rf /code/config.json /static/assets/config/config.json - {{- end }} - cp -Rf /code/robots.txt /static/robots.txt - env: - {{- include "angular.environment" . | indent 12 }} - volumeMounts: - - name: static - mountPath: /static - {{- if hasKey .Values.configFiles "config.json" }} - - name: config - mountPath: /code/config.json - subPath: config.json - readOnly: true - {{- end }} - - name: config - mountPath: /code/robots.txt - subPath: robots.txt - readOnly: true - containers: - - name: {{ .Values.nginx.name }} - image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" - imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} - command: - - nginx - - -c - - /etc/nginx/nginx.conf - - -g - - daemon off; - env: - {{- include "angular.environment" . | indent 12 }} - volumeMounts: - - mountPath: /static - name: static - readOnly: true - - mountPath: /etc/nginx/nginx.conf - name: config - subPath: nginx.conf - readOnly: true - {{- if .Values.nginx.volumeMounts }} - {{- toYaml .Values.nginx.volumeMounts | nindent 12 }} - {{- end }} - ports: - - containerPort: {{ .Values.service.internalPort }} - # livenessProbe: - # httpGet: - # path: /healthz - # port: {{ .Values.service.internalPort }} - readinessProbe: - httpGet: - path: / - port: {{ .Values.service.internalPort }} - resources: - {{- toYaml .Values.nginx.resources | nindent 12 }} - volumes: - - name: static - emptyDir: {} - - name: config - configMap: - name: {{ template "angular.fullname" . }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} diff --git a/angular-osf/templates/hpa.yaml b/angular-osf/templates/hpa.yaml deleted file mode 100644 index 0e257da3..00000000 --- a/angular-osf/templates/hpa.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "angular.fullname" . }} - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "angular.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} diff --git a/angular-osf/templates/ingress.yaml b/angular-osf/templates/ingress.yaml deleted file mode 100644 index edb7f00b..00000000 --- a/angular-osf/templates/ingress.yaml +++ /dev/null @@ -1,111 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "angular.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "angular.fullname" . }} - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - {{- if .Values.ingress.annotations }} - annotations: - {{- toYaml .Values.ingress.annotations | nindent 4 }} - {{- end }} -spec: - rules: - {{- $shareJSEnabled := .Values.sharejs.enabled -}} - {{- $shareJSServiceName := .Values.sharejs.service.name -}} - {{- $shareJSServicePort := .Values.sharejs.service.externalPort -}} - {{- $shareJSIngressPaths := .Values.sharejs.ingress.paths -}} - {{- $legacyEnabled := .Values.legacy.enabled -}} - {{- $legacyServiceName := .Values.legacy.service.name -}} - {{- $legacyServicePort := .Values.legacy.service.externalPort -}} - {{- $legacyIngressPaths := .Values.legacy.ingress.paths -}} - {{- range .Values.ingress.primaryHosts }} - - host: {{ . }} - http: - paths: - {{- if $shareJSEnabled }} - {{- range $shareJSIngressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $shareJSServiceName }} - port: - number: {{ $shareJSServicePort }} - {{- end }} - {{- end }} - {{- if $legacyEnabled }} - {{- range $legacyIngressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $legacyServiceName }} - port: - number: {{ $legacyServicePort }} - {{- end }} - {{- end }} - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end }} - {{- end -}} - {{- range .Values.ingress.additionalHosts }} - - host: {{ . }} - http: - paths: - {{- if $shareJSEnabled }} - {{- range $shareJSIngressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $shareJSServiceName }} - port: - number: {{ $shareJSServicePort }} - {{- end }} - {{- end }} - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end }} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls) .Values.additionalCertificates) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "angular.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- range $certificate := .Values.additionalCertificates }} - {{- $name := default $.Chart.Name $.Values.nameOverride }} - {{- $certificateFullName := (printf "%s-%s-%s" $.Release.Name $name $certificate.name | trunc 63 | trimSuffix "-") }} - - secretName: {{ $certificateFullName }} - hosts: - {{- range $certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end }} - {{- end -}} -{{- end -}} diff --git a/angular-osf/templates/main.yaml b/angular-osf/templates/main.yaml new file mode 100644 index 00000000..d5bd9150 --- /dev/null +++ b/angular-osf/templates/main.yaml @@ -0,0 +1,10 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.service" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.ingress" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.hpa" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pdb" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.certificate" (dict "root" . "name" "certificate" "values" .Values.main) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "" "values" .Values.main) }} + \ No newline at end of file diff --git a/angular-osf/templates/pdb.yaml b/angular-osf/templates/pdb.yaml deleted file mode 100644 index afa9e65d..00000000 --- a/angular-osf/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "angular.fullname" . }}" - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/angular-osf/templates/service.yaml b/angular-osf/templates/service.yaml deleted file mode 100644 index 19dc9fb3..00000000 --- a/angular-osf/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "angular.fullname" . }} - labels: - app: {{ template "angular.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "angular.name" . }} - release: {{ .Release.Name }} diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index 0af3e016..b6fa7894 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -1,146 +1,384 @@ -# Default values for osf-preprints. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -name: angular -replicaCount: 1 - -image: - repository: quay.io/centerforopenscience/angular-osf - tag: develop - pullPolicy: Always - -antiAffinity: soft - -budget: - minAvailable: 0 - -configEnvs: - MULTI_CONFIG: false - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -nginx: - name: nginx +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name +## =============== MAIN Component =============== +main: + enabled: true + + replicas: 1 + + http: + containers: + nginx: + internalPort: 80 + externalPort: 80 + serviceType: ClusterIP + +# ------- Configuration follows for containerName: nginx ------- image: repository: nginx tag: alpine pullPolicy: Always - primaryDomain: "" - modules: [] - proxySourceRanges: [] - realIpHeader: X-Real-IP - realIpRecursive: "off" - vts: - enabled: false - internalPort: 18080 - statusZoneSize: 10m - defaultFilterKey: "$geoip_country_code country::*" - - brandedSubdomains: [] - preprintDomainMap: {} - # domain.org: providerId - institutionDomainMap: {} - # institution.edu: institutionId - additionalRewrites: [] - # - rewrite "^/di8cg/{0,1}" https://$host/s9tya permanent; - # additionalConfig: |- - # ... - redirects: - enabled: false - domains: {} - # - to: example.com - # from: - # - www.example.com - # - another.domain.com + containerName: nginx + + command: + - nginx + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + + env: [] + + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "env") | trim }}' + + probes: + readiness: + httpGet: + path: / + port: "{{ .Values.main.http.containers.nginx.internalPort }}" + + ports: + - name: nginx + containerPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + volumeMounts: + - name: static + mountPath: /static + readOnly: true + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + + additionalVolumeMounts: [] resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -horizontalPodAutoscaler: - enabled: false - maxReplicas: 3 - targetCPUUtilizationPercentage: 90 - -service: - name: nginx - type: ClusterIP - externalPort: 80 - internalPort: 80 - -ingress: - enabled: false - # Used to create Ingress record (should used with service.type: ClusterIP). - primaryHost: - - chart-example.local - # Additional hosts will not have legacy path annotations - # additionalHosts: - # - chart-example-2.local - paths: - - / - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - tls: - # Secrets must be manually created in the namespace. - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -configFiles: {} - # Angular config (merges and overrides the default config in the container) - # config.json: |- - # { - # } - # robots.txt: |- - # # http://www.robotstxt.org - # User-agent: * - # Disallow: - -# Supporting services -share: - url: "share.osf.io" - -prerender: - enabled: false - service: - name: prerender - externalPort: 3000 -sharejs: - enabled: false - service: - name: sharejs - externalPort: 7007 - ingress: - paths: - - /sharejs +# ------- Init containers ------- + angular: + image: + repository: quay.io/centerforopenscience/angular-osf + tag: develop + pullPolicy: Always + resources: + limits: + cpu: 1 + memory: 128Mi + requests: + cpu: "0" + memory: 128Mi + + initContainers: + - name: angular + image: "{{ .Values.main.angular.image.repository }}:{{ .Values.main.angular.image.tag }}" + imagePullPolicy: "{{ .Values.main.angular.image.pullPolicy }}" + command: + - /bin/sh + - -c + - |- + set -e + cp -Rf /code/dist/osf/browser/* /static/ + {{- if hasKey .Values.main.configMap.data "config.json" }} + cp -Rf /config/config.json /static/assets/config/config.json + {{- end }} + cp -Rf /config/robots.txt /static/robots.txt + + env: [] -legacy: - enabled: false + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "env") | trim }}' + + volumeMounts: + - name: static + mountPath: /static + - name: config + mountPath: /config/config.json + subPath: config.json + readOnly: true + - name: config + mountPath: /config/robots.txt + subPath: robots.txt + readOnly: true + resources: + limits: + cpu: "{{ .Values.main.angular.resources.limits.cpu }}" + memory: "{{ .Values.main.angular.resources.limits.memory }}" + requests: + cpu: "{{ .Values.main.angular.resources.requests.cpu }}" + memory: "{{ .Values.main.angular.resources.requests.memory }}" + + additionalInitContainers: [] + + +# ------- Additional containers ------- + additionalContainers: [] + + sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: static + emptyDir: {} + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + additionalVolumes: [] + + +# ------- Affitnity configuration ------- + affinity: {} + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 1 + # podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case (always for main) component name = chart name, because we leave name in main.yaml empty. + + additionalAffinities: [] + # - nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: cloud.google.com/gke-preemptible + # operator: In + # values: + # - "true" + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" + + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' service: - name: osf-web - externalPort: 5000 + enabled: true + type: "{{ .Values.main.http.containers.nginx.serviceType }}" + ports: + - name: nginx + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + targetPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' ingress: - paths: - - /api/v1 + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + secondary: + - chart-example-2.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: true + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + paths: + - / + + - name: legacy + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: osf-web + externalPort: 5000 + paths: + - /api/v1 + + - name: s1sharejs + includeForPrimaryHost: true + includeForSecondaryHost: true + pathType: ImplementationSpecific + service: + name: sharejs + externalPort: 7007 + paths: + - /sharejs + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local + + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + + # additionalCertificates: + # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name + # - name: example-org-cert + # enabled: false + # secretName: secret-with-cert + # commonName: example.org + # dnsNames: + # - example.org + # - submdomain.example.org + # issuerRef: + # name: letsencrypt-prod + # kind: ClusterIssuer + # acmeConfig: + # http01: {} + # # ingress: '' + # domains: + # - example.org + # - subdomain.example.org + + +# ------- HPA configuration ------- + hpa: + enabled: false + minReplicas: "{{ .Values.main.replicas }}" + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + + +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 1 + + +# ------- Network Policy configuration ------- +# Network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + networkPolicy: + enabled: false + allowEgress: false + ingressRules: + - from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "client") | trim }}': "true" + ports: + - port: "{{ .Values.main.http.containers.nginx.internalPort }}" + - ports: + - port: "{{ .Values.main.nginx.vts.internalPort }}" + egressRules: + - {} + +# Additional network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalNetworkPolicies: + - name: cert-solver-example + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- ConfigMap configuration ------- +# ConfigMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + configMap: + enabled: true + tpl: true + data: + robots.txt: | + {{ .Files.Get "files/robots.txt" }} + config.json: |- + { + } + nginx.conf: | + {{ tpl (.Files.Get "files/nginx.conf") (dict "Values" .Values "root" .) }} + +# Additional configMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalConfigMaps: + - name: env + enabled: true + tpl: false + data: + MULTI_CONFIG: "false" + + +# ------- Secrets configuration ------- +# Secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: false + includeTls: false + data: + superSecret: "secret" + fileExample.txt: |- + MySecretFile + + # additionalSecrets: + ## Additional secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + # - name: additional + # enabled: false + # includeTls: false + # data: + # superSecret: "secret" + + +# ------- Selectors and etc. ------- + nodeSelector: {} + + +# ------- Other variables which are used accross files or values.yaml ------- + nginx: + modules: [] + proxySourceRanges: [] + realIpHeader: X-Real-IP + realIpRecursive: "off" + vts: + enabled: false + internalPort: 18080 + statusZoneSize: 10m + defaultFilterKey: "$geoip_country_code country::*" + brandedSubdomains: [] + primaryDomain: "" + preprintDomainMap: {} + institutionDomainMap: {} + additionalRewrites: [] + additionalConfig: "" + # Example additional rewrites (uncomment to use): + # additionalRewrites: + # - rewrite "^/di8cg/{0,1}" https://$host/s9tya permanent; + # - rewrite "^/legacy-path/(.*)" https://$host/new/$1 permanent; + # - rewrite "^/old-section$" /index.html break; + redirects: + enabled: false + domains: [] + + share: + url: "share.osf.io" + + prerender: + enabled: false + service: + name: prerender + externalPort: 3000 diff --git a/cos-common/Chart.yaml b/cos-common/Chart.yaml new file mode 100644 index 00000000..3dff3e07 --- /dev/null +++ b/cos-common/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: cos-common +type: library +version: 1.0.0 +kubeVersion: ">=1.26.0-0" +description: Reusable building blocks for platform application charts. diff --git a/cos-common/README.md b/cos-common/README.md new file mode 100644 index 00000000..4839ad5a --- /dev/null +++ b/cos-common/README.md @@ -0,0 +1,159 @@ +cos-common +========== + +Reusable Helm **library** chart that bundles the common building blocks used by Center for Open Science application charts. You do **not** install it on its own; instead, your application chart imports it and renders the pieces you need (workloads, traffic, config, secrets, network policy, certs, etc.). + +Helm: v3 (library chart). Kubernetes: `>=1.26` (see `Chart.yaml`). + +## Contents +- Workloads: `deployment`, `statefulset`, `job`, `cronjob` +- Traffic: `service`, `ingress` +- Ops: `hpa`, `pdb`, `networkpolicy` +- Config: `configmap`, `secret` (auto base64, optional TLS merge), cert-manager `certificate` +- Helpers: naming/labels, pod spec builder (env, probes, volumes, init/sidecars, pod options), tpl-aware map/list rendering, checksum helper + +## Adding the dependency +In your application `Chart.yaml`: + +```yaml +apiVersion: v2 +name: my-app +version: 0.1.0 +dependencies: + - name: cos-common + version: 0.1.0 + repository: "file://../cos-common" + # OR as a package + - name: cos-common + version: 0.1.0 + repository: https://centerforopenscience.github.io/helm-charts/ +``` + +Run `helm dependency update` so the library is available during rendering. + +## How to use the templates +Call the templates you need from your chart under `templates/`. Each include takes: +- `root`: the parent Helm context (usually `.`) +- `name`: component name (used for naming/labels) +- `values`: the component values block + +```yaml +# templates/app.yaml +{{- include "cos-common.configmap" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.secret" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.deployment" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.service" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.ingress" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.hpa" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.pdb" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.pvc" (dict "root" . "name" "app" "values" .Values.app) }} +{{- include "cos-common.certificate" (dict "root" . "name" "app" "values" .Values.app) }} +``` + +Use `cos-common.statefulset`, `cos-common.job`, or `cos-common.cronjob` the same way when you need those workload types. + +## Template reference (what each tpl renders) +- `cos-common.deployment`: Deployment with component labels/selectors, strategy, replicas, pod template built from component values. +- `cos-common.statefulset`: StatefulSet with serviceName, update/retention policies, volumeClaimTemplates, pod template. +- `cos-common.job`: Batch Job with parallelism/completions/backoff, pod template. +- `cos-common.cronjob`: CronJob with required `schedule`, job history limits, job spec/pod template (shares the same job spec fields as `cos-common.job`). +- `cos-common.service`: Service with type/ports, optional LB fields, selector wired to component labels. +- `cos-common.ingress`: Ingress with hosts+paths or defaultBackend, TLS, ingressClassName; backend defaults to the component service and can shift to a maintenance service when `.Values.maintenance.enabled` is true. +- `cos-common.hpa`: HorizontalPodAutoscaler pointing to the component deployment/statefulset; requires min/max/metrics when enabled. +- `cos-common.pdb`: PodDisruptionBudget with minAvailable or maxUnavailable. +- `cos-common.pvc`: PersistentVolumeClaims when `persistence.enabled` (component-level) or `volumes[].persistence.enabled`; auto-skips `existingClaim`; supports size/class/selector and spec overrides. +- `cos-common.networkpolicy`: NetworkPolicy defaulting to namespace-local ingress allow; optional egress and extra ingress/egress rules; supports additional named policies. +- `cos-common.configmap`: Main ConfigMap (tpl-aware) plus `additionalConfigMaps[]`. +- `cos-common.secret`: Main Secret (auto base64, optional tls merge) plus `additionalSecrets[]`. +- `cos-common.certificate`: cert-manager Certificate from `certificate` block plus `additionalCertificates[]`. +- Helpers: `cos-common.componentChecksum` to hash rendered resources; label/name helpers; pod spec builder for containers/init/sidecars/volumes, etc. + +## Values layout and expectations +Define one top-level block per component (`app`, `worker`, `migration`, etc.). A minimal workload needs an `image.repository`; everything else is optional/opt-in. `.Values.global` is intentionally open: put shared settings there (URLs, credentials, defaults) and reference them from components; nothing renders from `global` automatically. + +```yaml +global: + dbPassword: superpass + +app: + image: + repository: ghcr.io/example/app + tag: v1.2.3 + replicas: 2 + ports: + - name: http + containerPort: 8080 + service: + enabled: true + ports: + - port: 80 + targetPort: http + ingress: + enabled: true + hosts: + - host: example.org + paths: + - path: / + pathType: Prefix + port: http + hpa: + enabled: true + minReplicas: 2 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 75 + pdb: + enabled: true + minAvailable: 1 + networkPolicy: + enabled: true + allowEgress: true + configMap: + enabled: true + tpl: true + data: + APP_MODE: production + secret: + enabled: true + data: + password: "{{ .Values.global.dbPassword }}" + certificate: + enabled: true + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + dnsNames: [example.org] +``` + +### Notable behaviors +- **Enablement**: `enabled: false` on the component skips all its resources. Most sub-blocks (`service`, `ingress`, `hpa`, `pdb`, etc.) also have their own `enabled` flag. +- **Naming/labels**: standard Helm labels are applied; `fullnameOverride` works at both component and sub-resource level. Names are trimmed to 63 chars. +- **ConfigMap**: `tpl: true` renders `.data` through Helm’s engine. `additionalConfigMaps[]` lets you emit extra ConfigMaps without new templates. +- **Secret**: `.data` is auto base64’d. `includeTls: true` can merge TLS files from `.Values.tls.*.files`. `additionalSecrets[]` is supported. +- **Ingress**: requires `hosts` or `defaultBackend`. Besides the legacy `hosts[]` block, you can use grouped hosts via `hosts.primary`/`hosts.secondary` with `rules[]` (per-rule enablement and `includeForPrimaryHost`/`includeForSecondaryHost`) to fan out shared path sets across host groups; `servicePort` defaults to `service.ports[0]` when not set on a path/backend. +- **Maintenance**: when `.Values.maintenance.enabled` is true, ingress targets the maintenance service/port (`maintenance.service.externalPort` or `maintenance.servicePort`) instead of the component service. +- **Affinity**: use `affinity` for your base rules and `additionalAffinities[]` to layer on more affinity snippets; later entries override earlier keys. +- **HPA**: when `enabled`, `minReplicas`, `maxReplicas`, and `metrics` are required. +- **PDB**: when `enabled`, set either `minAvailable` or `maxUnavailable` (not both). +- **NetworkPolicy**: defaults to namespace-local ingress allow; egress only if `allowEgress: true` or `extraEgressRules` present. Use `componentScoped: false` to drop the component label when you want one policy to cover multiple components. `additionalNetworkPolicies[]` supported. +- **Certificates**: renders cert-manager `Certificate`; `issuerRef` required when enabled. `additionalCertificates[]` available. +- **Persistence**: component-level `persistence` or `volumes[].persistence` can auto-create PVCs (unless `existingClaim`); per-volume persistence forbids `emptyDir` and wires the volume to the claim automatically. +- **CronJob**: `schedule` is required; job-spec knobs (`parallelism`, `backoffLimit`, `podFailurePolicy`, etc.) live directly under the component block. +- **Annotations**: `annotations` apply broadly by default; set `annotationsWorkloadOnly: true` or use `workloadAnnotations` to scope/override annotations on the workload resources only (deployment/statefulset/job/cronjob), e.g., for Helm hooks. +- **StatefulSet**: define `serviceName` and `volumeClaimTemplates` as needed; supports `persistentVolumeClaimRetentionPolicy`. +- **Checksums**: `cos-common.componentChecksum` can hash a rendered resource (configmap/secret/etc.) for restart-on-change annotations. + +## Tips +- Keep component names short; helpers trim names to 63 chars for DNS compatibility. +- Use `.Values.global` for shared knobs and reference them with `{{ .Values.global.* }}` or via `tpl`. +- When annotating a pod template for restarts on config changes, use something like `checksum/config: {{ include "cos-common.componentChecksum" (dict "root" . "name" "app" "values" .Values.app "resource" "configmap") }}`. +- For CronJobs, set `schedule` and any job fields you need directly under the component block. +- For StatefulSets, set `serviceName` and `volumeClaimTemplates` when you need stable identities. + +## Example chart +See `angular-osf/templates/main.yaml` and `angular-osf/values.yaml` in this repo for a complete working example that wires together config map, secret, deployment, service, ingress, HPA, PDB, and certificate using this library. diff --git a/cos-common/templates/_certificate.tpl b/cos-common/templates/_certificate.tpl new file mode 100644 index 00000000..bbfddf72 --- /dev/null +++ b/cos-common/templates/_certificate.tpl @@ -0,0 +1,136 @@ +{{/* ============================================================================ + Build certificate spec (prints YAML; use with fromYaml). + Copies only supported cert-manager fields from input into a clean spec map. + ============================================================================ */}} +{{- define "cos-common.buildCertSpec" -}} +{{- $src := .src -}} +{{- $secretName := .secretName -}} +{{- $spec := dict "secretName" $secretName -}} + +{{- $fields := list + "commonName" + "dnsNames" + "ipAddresses" + "uris" + "emailAddresses" + "issuerRef" + "duration" + "renewBefore" + "usages" + "privateKey" + "subject" + "secretTemplate" +-}} + +{{- range $field := $fields }} + {{- with get $src $field }} + {{- $_ := set $spec $field . }} + {{- end }} +{{- end }} + +{{- toYaml $spec -}} +{{- end }} + + + +{{/* ============================================================================ + Render Certificate resource with the provided spec, name overrides, labels, and annotations. + ============================================================================ */}} +{{- define "cos-common.renderCertificate" -}} +{{- $root := .root -}} +{{- $name := .name -}} +{{- $fullnameOverride := .fullnameOverride -}} +{{- $labels := default dict .labels -}} +{{- $annotations := default dict .annotations -}} +{{- $spec := default dict .spec -}} + +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + {{- include "cos-common.metadata" + (dict + "root" $root + "name" $name + "values" (dict + "fullnameOverride" $fullnameOverride + "labels" $labels + "annotations" $annotations + ) + ) | nindent 2 + }} +spec: + {{- tpl (toYaml $spec) $root | nindent 2 }} +{{- end }} + + + +{{/* ============================================================================ + Main + Additional Certificates (supports one primary and any number of additional entries). + ============================================================================ */}} +{{- define "cos-common.certificate" -}} + +{{- $vals := default dict .values -}} +{{- $cert := default dict $vals.certificate -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} + +{{/* ============================================================================ + Main certificate + ============================================================================ */}} +{{- if and $componentEnabled (default false $cert.enabled) }} + + {{- if not $cert.issuerRef }} + {{ fail (printf "component %s.certificate.issuerRef must be set when certificate.enabled=true" .name) }} + {{- end }} + + {{- $labels := merge dict (default dict $vals.labels) (default dict $cert.labels) -}} + {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $cert.annotations) | fromJson -}} + + {{- $baseName := include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals) -}} + {{- $name := default $baseName $cert.name -}} + {{- $secretName := default $name $cert.secretName -}} + + {{- $spec := (include "cos-common.buildCertSpec" + (dict "src" $cert "secretName" $secretName) + ) | fromYaml + -}} + + {{- include "cos-common.renderCertificate" (dict + "root" .root + "name" .name + "fullnameOverride" (coalesce $cert.fullnameOverride $vals.fullnameOverride $name) + "labels" $labels + "annotations" $annotations + "spec" $spec + ) }} +{{- end }} + + + +{{/* ============================================================================ + Additional certificates + ============================================================================ */}} +{{- if $componentEnabled }} + {{- $root := .root -}} + {{- $component := .name -}} + {{- $releaseName := include "cos-common.releaseName" (dict "root" $root) -}} + {{- $chartName := include "cos-common.chartName" (dict "root" $root) -}} + {{- $prefix := printf "%s-%s-%s-" $releaseName $chartName $component -}} + {{- $items := list -}} + {{- range $item := default list $vals.additionalCertificates }} + {{- $items = append $items (merge (dict "name" (default $item.name $item.secretName)) $item) }} + {{- end }} + {{- include "cos-common.renderAdditionalResources" (dict + "root" $root + "component" $component + "values" $vals + "items" $items + "namePrefix" $prefix + "error" (printf "component %s.additionalCertificates entry requires name or secretName" $component) + "renderer" "cos-common.additionalCertificateResource" + ) + }} + +{{- end }} + +{{- end }} diff --git a/cos-common/templates/_configmap.tpl b/cos-common/templates/_configmap.tpl new file mode 100644 index 00000000..cb6352ee --- /dev/null +++ b/cos-common/templates/_configmap.tpl @@ -0,0 +1,93 @@ +{{/* ============================================================================ + Main ConfigMap + Additional ConfigMaps. + Supports templating data (tpl=true) and keeps binaryData separate. + ============================================================================ */}} +{{- define "cos-common.configmap" -}} + +{{- $vals := default dict .values -}} +{{- $cfg := default dict $vals.configMap -}} +{{- $root := .root -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $render := and $componentEnabled (default false $cfg.enabled) -}} + +{{/* ============================================================================ + MAIN CONFIGMAP + ============================================================================ */}} +{{- if $render }} + +{{- $labels := merge dict (default dict $vals.labels) (default dict $cfg.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $cfg.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $cfg.name $cfg.fullnameOverride $vals.fullnameOverride -}} +{{- $data := default dict $cfg.data -}} +{{- $binaryData := default dict $cfg.binaryData -}} + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + {{- include "cos-common.metadata" + (dict "root" .root "name" .name "values" + (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations) + ) | nindent 2 + }} + {{- with $cfg.ownerReferences }} + ownerReferences: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{- end }} + +data: +{{- /* Empty map? Render {} to avoid null coercion in Helm. */ -}} +{{- if eq (len $data) 0 }} + {} +{{- else if $cfg.tpl }} + {{- /* tpl=true: evaluate string values against the root context. */ -}} + {{- $renderedData := dict }} + {{- range $key, $value := $data }} + {{- if kindIs "string" $value }} + {{- $_ := set $renderedData $key (tpl $value $.root) }} + {{- else }} + {{- $_ := set $renderedData $key $value }} + {{- end }} + {{- end }} +{{ toYaml $renderedData | nindent 2 }} +{{- else }} +{{ toYaml $data | nindent 2 }} +{{- end }} + +{{- if gt (len $binaryData) 0 }} +binaryData: +{{ toYaml $binaryData | nindent 2 }} +{{- end }} + +{{- with $cfg.immutable }} +immutable: {{ . }} +{{- end }} + +{{- end }}{{/* end MAIN CONFIGMAP */}} + + + +{{/* ============================================================================ + ADDITIONAL CONFIGMAPS + Name rule: + - fullnameOverride → directly use it + - name → chartName + "-" + name + ============================================================================ */}} +{{- if $componentEnabled }} + + {{- $baseName := include "cos-common.fullname" (dict "root" $root "name" .name "values" $vals) | trim -}} + {{- $namePrefix := printf "%s-" $baseName -}} + {{- include "cos-common.renderAdditionalResources" (dict + "root" $root + "component" .name + "values" $vals + "items" $vals.additionalConfigMaps + "namePrefix" $namePrefix + "error" "additionalConfigMaps entry must have either name or fullnameOverride" + "renderer" "cos-common.additionalConfigMapResource" + ) + }} + +{{- end }}{{/* end componentEnabled */}} + +{{- end }}{{/* end define */}} diff --git a/cos-common/templates/_cronjob.tpl b/cos-common/templates/_cronjob.tpl new file mode 100644 index 00000000..229ca3b8 --- /dev/null +++ b/cos-common/templates/_cronjob.tpl @@ -0,0 +1,55 @@ +{{/* Render a CronJob for the component, reusing podSpec helpers and component enablement. */}} +{{- define "cos-common.cronjob" -}} +{{- $vals := default dict .values -}} +{{- $enabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if $enabled }} +{{- if not $vals.schedule }} +{{- fail (printf "component %s.schedule is required for cronjob" .name) }} +{{ end }} +{{- $jobTemplate := default dict $vals.jobTemplate -}} +{{- $jobTplLabels := default (dict) $jobTemplate.labels -}} +{{- $jobTplAnnotations := include "cos-common.annotations" (dict "values" $vals "resource" $jobTemplate.annotations "isWorkload" true) | fromJson -}} +{{- $fullnameOverride := $vals.fullnameOverride -}} +{{- $cronAnnotations := include "cos-common.annotations" (dict "values" $vals "isWorkload" true) | fromJson -}} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $vals.labels "annotations" $cronAnnotations)) | nindent 2 }} +spec: + {{- /* Required schedule drives how often the jobTemplate runs. */}} + schedule: {{ quote $vals.schedule }} + {{- with $vals.timeZone }} + timeZone: {{ . }} + {{ end }} + {{- with $vals.concurrencyPolicy }} + concurrencyPolicy: {{ . }} + {{ end }} + {{- with $vals.suspend }} + suspend: {{ . }} + {{ end }} + {{- with $vals.startingDeadlineSeconds }} + startingDeadlineSeconds: {{ . }} + {{ end }} + {{- with $vals.successfulJobsHistoryLimit }} + successfulJobsHistoryLimit: {{ . }} + {{ end }} + {{- with $vals.failedJobsHistoryLimit }} + failedJobsHistoryLimit: {{ . }} + {{ end }} + jobTemplate: + metadata: + labels: + {{- include "cos-common.labels" . | nindent 8 }} + {{- if gt (len $jobTplLabels) 0 }} + {{ tpl (toYaml $jobTplLabels) $.root | nindent 8 }} + {{ end }} + {{- if gt (len $jobTplAnnotations) 0 }} + annotations: + {{ tpl (toYaml $jobTplAnnotations) $.root | nindent 8 }} + {{ end }} + spec: + {{- /* Reuse the same jobSpec helper so one-off Job and CronJob pods stay aligned. */}} + {{- include "cos-common.jobSpec" (dict "root" .root "name" .name "values" $vals "suspend" $vals.jobSuspend) | nindent 6 }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_deployment.tpl b/cos-common/templates/_deployment.tpl new file mode 100644 index 00000000..115bea9e --- /dev/null +++ b/cos-common/templates/_deployment.tpl @@ -0,0 +1,44 @@ +{{/* Render a Deployment for the component, wiring in the shared pod spec pieces. */}} +{{- define "cos-common.deployment" -}} +{{- $vals := default dict .values -}} +{{- $enabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if $enabled }} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "isWorkload" true) | fromJson -}} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + {{- /* Merge base values with workload annotations so pod and owner share them. */}} + {{- $metadataVals := merge (dict) $vals (dict "annotations" $annotations) -}} + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" $metadataVals) | nindent 2 }} +spec: + {{- /* Simple rollout defaults; caller can override counts and timing. */}} + replicas: {{ default 1 $vals.replicas }} + {{- with $vals.revisionHistoryLimit }} + revisionHistoryLimit: {{ . }} + {{ end }} + {{- with $vals.minReadySeconds }} + minReadySeconds: {{ . }} + {{ end }} + {{- with $vals.paused }} + paused: {{ . }} + {{ end }} + {{- with $vals.progressDeadlineSeconds }} + progressDeadlineSeconds: {{ . }} + {{ end }} + selector: + matchLabels: + {{- include "cos-common.selectorLabels" . | nindent 6 }} + {{- with $vals.strategy }} + strategy: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} + template: + metadata: + {{- /* Pod labels/annotations stay aligned with selectors. */}} + {{- include "cos-common.podMetadata" . | nindent 6 }} + spec: + {{- /* Shared pod spec helper wires containers, volumes, TLS, affinities, etc. */}} + {{- include "cos-common.podSpec" . | nindent 6 }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl new file mode 100644 index 00000000..59e70590 --- /dev/null +++ b/cos-common/templates/_helpers.tpl @@ -0,0 +1,988 @@ +{{/* +Common helpers for the cos-common library chart. +*/}} + +{{/* +Return true when the component values exist and are enabled. +*/}} +{{- define "cos-common.componentEnabled" -}} +{{- $vals := .values -}} +{{- if and $vals (ne (default true $vals.enabled) false) -}} +true +{{- else -}} +false +{{- end }} +{{- end }} + +{{/* +Parse init-certs config (supports boolean for backward compatibility). +*/}} +{{- define "cos-common.initCertConfig" -}} +{{- $vals := default dict .values -}} +{{- $cfg := default (dict) $vals.enabledInitContainersCertificate -}} +{{- if kindIs "bool" $vals.enabledInitContainersCertificate }} + {{- $cfg = dict "enabled" $vals.enabledInitContainersCertificate -}} +{{- end }} +{{- $enabled := default false $cfg.enabled -}} +{{- $mount := $cfg.mountToContainer -}} +{{- $owner := default "www-data:www-data" $cfg.userCertsOwner -}} +{{- dict "enabled" $enabled "mountToContainer" $mount "userCertsOwner" $owner -}} +{{- end }} + +{{/* +Return TLS configs when global TLS is enabled and the component secret opts in. +*/}} +{{- define "cos-common.enabledInitContainersCertificate" -}} +{{- $vals := default dict .values -}} +{{- $sec := default dict $vals.secret -}} +{{- $tls := default dict .root.Values.tls -}} +{{- $componentEnabled := and $vals (ne (default true $vals.enabled) false) -}} +{{- $initCfg := include "cos-common.initCertConfig" (dict "values" $vals) | fromYaml -}} +{{- $includeTls := or (default false $sec.includeTls) (default false $initCfg.enabled) -}} +{{- /* Allow TLS init/volumes even when main.secret.enabled=false (for externally-managed secrets). */ -}} +{{- if and $componentEnabled (default false $tls.enabled) $includeTls -}} + {{- $enabled := dict -}} + {{- /* Collect only tls.* entries explicitly enabled so we can mount them. */ -}} + {{- range $app, $cfg := omit $tls "enabled" }} + {{- $isMap := kindIs "map" $cfg }} + {{- if and $cfg $isMap (default false (index $cfg "enabled")) }} + {{- $_ := set $enabled $app (merge $cfg (dict "mountToContainer" $initCfg.mountToContainer "userCertsOwner" $initCfg.userCertsOwner)) }} + {{- end }} + {{- end }} + {{- if gt (len $enabled) 0 }} +{{ toYaml $enabled }} + {{- end }} +{{- end }} +{{- end }} + +{{/* +Trim any name to <=63 characters and remove trailing hyphen. +*/}} +{{- define "cos-common.trim63" -}} +{{- trunc 63 . | trimSuffix "-" -}} +{{ end }} + +{{/* +Resolve the chart name, honoring nameOverride. +*/}} +{{- define "cos-common.chartName" -}} +{{- $root := .root -}} +{{- $name := default $root.Chart.Name $root.Values.nameOverride -}} +{{- include "cos-common.trim63" $name -}} +{{ end }} + +{{/* +Resolve the Helm chart version label. +*/}} +{{- define "cos-common.chartVersion" -}} +{{- $root := .root -}} +{{- $sanitizedVersion := replace "+" "_" $root.Chart.Version -}} +{{- $label := printf "%s-%s" $root.Chart.Name $sanitizedVersion -}} +{{- include "cos-common.trim63" $label -}} +{{ end }} + +{{/* +Resolve the release base name, honoring fullnameOverride. +*/}} +{{- define "cos-common.releaseName" -}} +{{- $root := .root -}} +{{- $name := $root.Release.Name -}} +{{- if $root.Values.fullnameOverride }} +{{- $name = tpl $root.Values.fullnameOverride $root -}} +{{ end }} +{{- include "cos-common.trim63" $name -}} +{{ end }} + +{{/* +Compute the component fullname (--) +with optional overrides. +*/}} +{{- define "cos-common.fullname" -}} +{{- $vals := default dict .values -}} +{{- if $vals.fullnameOverride }} + {{- include "cos-common.trim63" (tpl $vals.fullnameOverride .root) -}} +{{- else -}} + {{- $release := include "cos-common.releaseName" (dict "root" .root) -}} + {{- $chart := .root.Chart.Name -}} + {{- $component := .name -}} + + {{- $fullname := printf "%s-%s-%s" $release $chart $component -}} + {{- include "cos-common.trim63" $fullname -}} +{{- end }} +{{- end }} + + +{{/* +Resolve a non-empty component name for labels/containers. +If .name is empty, fall back to the chart name. +*/}} +{{- define "cos-common.componentName" -}} +{{- if .name -}} +{{ .name }} +{{- else -}} +{{- include "cos-common.chartName" (dict "root" .root) -}} +{{- end -}} +{{ end }} + +{{/* +Common labels shared across objects. +*/}} +{{- define "cos-common.labels" -}} +app.kubernetes.io/name: {{ include "cos-common.chartName" (dict "root" .root) }} +app.kubernetes.io/instance: {{ .root.Release.Name }} +app.kubernetes.io/managed-by: {{ .root.Release.Service }} +app.kubernetes.io/component: {{ include "cos-common.componentName" . }} +{{- with .root.Chart.AppVersion }} +app.kubernetes.io/version: {{ quote . }} +{{ end }} +app.kubernetes.io/part-of: {{ include "cos-common.chartName" (dict "root" .root) }} +helm.sh/chart: {{ include "cos-common.chartVersion" (dict "root" .root) }} +{{ end }} + +{{/* +Labels applied to selectors. +*/}} +{{- define "cos-common.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cos-common.chartName" (dict "root" .root) }} +app.kubernetes.io/instance: {{ .root.Release.Name }} +app.kubernetes.io/component: {{ include "cos-common.componentName" . }} +{{ end }} + +{{/* +Labels applied to pods (match selectors). +*/}} +{{- define "cos-common.podLabels" -}} +{{- include "cos-common.selectorLabels" . }} +{{ end }} + +{{/* +Compose image reference supporting tag or digest. +*/}} +{{- define "cos-common.image" -}} +{{- $img := default dict .image -}} +{{- $root := default dict .root -}} +{{- if not $img.repository }} +{{- fail (printf "component %s requires image.repository" .name) -}} +{{ end }} +{{- $repo := tpl (toString $img.repository) $root -}} +{{- $tag := "" -}} +{{- $digest := "" -}} +{{- if $img.tag }}{{- $tag = tpl (toString $img.tag) $root -}}{{- end -}} +{{- if $img.digest }}{{- $digest = tpl (toString $img.digest) $root -}}{{- end -}} +{{- if $img.digest }} +{{- printf "%s@%s" $repo $digest -}} +{{- else if $img.tag }} +{{- printf "%s:%s" $repo $tag -}} +{{- else -}} +{{- printf "%s:latest" $repo -}} +{{ end }} +{{ end }} + +{{/* +Render a generic metadata block, merging labels and annotations. +*/}} +{{- define "cos-common.metadata" -}} +name: {{ include "cos-common.fullname" . | trim }} +labels: + {{- include "cos-common.labels" . | nindent 2 }} + {{- with .values.labels }} + {{ tpl (toYaml .) .root | nindent 2 }} + {{ end }} +{{- with .values.annotations }} +annotations: + {{- $anns := tpl (toYaml .) .root | trimSuffix "\n" }} + {{ $anns | nindent 2 }} +{{ end }} +{{ end }} + +{{/* +Merge component annotations with resource-specific annotations. +Honors workload-only scoping via `annotationsWorkloadOnly` and allows +workload-specific additions via `workloadAnnotations`. +*/}} +{{- define "cos-common.annotations" -}} +{{- $values := default (dict) .values -}} +{{- $resourceAnns := default (dict) .resource -}} +{{- $isWorkload := default false .isWorkload -}} +{{- $componentAnns := default (dict) $values.annotations -}} +{{- if and (default false $values.annotationsWorkloadOnly) (not $isWorkload) }} + {{- $componentAnns = dict -}} +{{- end }} +{{- if $isWorkload }} + {{- $componentAnns = merge (dict) $componentAnns (default (dict) $values.workloadAnnotations) -}} +{{- end }} +{{- $annotations := merge (dict) $componentAnns $resourceAnns -}} +{{- $annotations | toJson -}} +{{- end }} + +{{/* +Render and hash a component resource (secret/configmap/etc). +Usage: + {{ include "cos-common.componentChecksum" (dict "root" . "name" "web" "values" .Values.web "resource" "secret") }} +*/}} +{{- define "cos-common.componentChecksum" -}} +{{- $template := .template -}} +{{- if not $template }} + {{- $resource := default "secret" .resource -}} + {{- $templates := dict + "secret" "cos-common.secret" + "configmap" "cos-common.configmap" + "deployment" "cos-common.deployment" + "statefulset" "cos-common.statefulset" + "service" "cos-common.service" + "ingress" "cos-common.ingress" + "pdb" "cos-common.pdb" + "networkpolicy" "cos-common.networkpolicy" + "certificate" "cos-common.certificate" + "hpa" "cos-common.hpa" + "job" "cos-common.job" + "cronjob" "cos-common.cronjob" + -}} + {{- $template = get $templates $resource -}} + {{- if not $template }} + {{- fail (printf "unknown resource '%s' for cos-common.componentChecksum" $resource) -}} + {{- end }} +{{- end }} +{{- $render := include $template (dict "root" .root "name" .name "values" .values) -}} +{{- if $render }} +{{- $checksum := trimSuffix "\n" (sha256sum $render) -}} +{{- if $checksum }} +{{- $checksum -}} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Render pod metadata labels and annotations. +*/}} +{{- define "cos-common.podMetadata" -}} +labels: + {{- include "cos-common.podLabels" . | nindent 2 }} + {{- with .values.podLabels }} + {{ tpl (toYaml .) .root | nindent 2 }} + {{ end }} +{{- with .values.podAnnotations }} +annotations: + {{- $podAnns := tpl (toYaml .) .root | trimSuffix "\n" }} + {{ $podAnns | nindent 2 }} +{{ end }} +{{ end }} + +{{/* +Helper to render list values with tpl evaluation. +*/}} +{{- define "cos-common.renderList" -}} +{{- with .list }} +{{ tpl (toYaml .) $.root }} +{{ end }} +{{ end }} + +{{/* +Helper to render map values with tpl evaluation. +*/}} +{{- define "cos-common.renderMap" -}} +{{- with .map }} +{{ tpl (toYaml .) $.root }} +{{ end }} +{{ end }} + +{{/* +Normalize a port map by templating values and casting numeric strings to integers. +*/}} +{{- define "cos-common.normalizePortMap" -}} +{{- $port := tpl (toYaml .port) .root | fromYaml }} +{{- range $field := (list "port" "targetPort" "containerPort" "hostPort" "nodePort") }} + {{- $val := get $port $field }} + {{- if and $val (kindIs "string" $val) (regexMatch "^\\d+$" (toString $val)) }} + {{- $_ := set $port $field (int $val) }} + {{- end }} +{{- end }} +{{- $port | toJson -}} +{{ end }} + +{{/* +Normalize a probe map and cast httpGet/tcpSocket port strings of digits to integers. +Keeps probes usable when values are templated as strings. +*/}} +{{- define "cos-common.normalizeProbe" -}} +{{- $probe := tpl (toYaml .probe) .root | fromYaml }} +{{- with $probe.httpGet }} + {{- $p := .port }} + {{- if and $p (regexMatch "^\\d+$" (toString $p)) }} + {{- $_ := set . "port" (int $p) }} + {{- end }} +{{- end }} +{{- with $probe.tcpSocket }} + {{- $p := .port }} + {{- if and $p (regexMatch "^\\d+$" (toString $p)) }} + {{- $_ := set . "port" (int $p) }} + {{- end }} +{{- end }} +{{- toYaml $probe -}} +{{ end }} + +{{/* +Render a service port block, templating strings and converting digit-only strings to numbers. +Accepts either a scalar (name/number) or a port map. +*/}} +{{- define "cos-common.renderServicePort" -}} +{{- $root := .root -}} +{{- $port := .port -}} +{{- if kindIs "map" $port }} + {{- tpl (toYaml $port) $root -}} +{{- else if kindIs "string" $port }} + {{- $val := tpl $port $root -}} + {{- if regexMatch "^\\d+$" (toString $val) }} +number: {{ int $val }} + {{- else }} +name: {{ $val }} + {{- end }} +{{- else }} +number: {{ $port }} +{{- end }} +{{ end }} + +{{/* +Build copy script for TLS certificates. +*/}} +{{- define "cos-common.tlsCopyScript" -}} +{{- $configs := .configs -}} +{{- $owner := default "www-data:www-data" .owner -}} +{{- range $app, $cfg := $configs }} +cp -f /certs/{{ $app }}/* {{ $cfg.mountPath }} +chown -R {{ $owner }} {{ $cfg.mountPath }} +chmod 0700 {{ $cfg.mountPath }} +chmod -R 0600 {{ $cfg.mountPath }}/* +{{- end }} +{{- end }} + +{{/* +Main container specification based on component values. +*/}} +{{- define "cos-common.mainContainer" -}} +{{- $vals := .values -}} +{{- $fallbackName := include "cos-common.componentName" . -}} +{{- $containerName := default (default .name $fallbackName) $vals.containerName -}} +{{- if and $.root (kindIs "string" $containerName) -}} + {{- $containerName = tpl $containerName $.root -}} +{{- end -}} +{{- $probes := default (dict) $vals.probes -}} +{{- $tlsConfigs := default (dict) .tlsConfigs -}} +{{- $pullPolicy := default "IfNotPresent" $vals.image.pullPolicy -}} +{{- if and (kindIs "string" $pullPolicy) $.root -}} + {{- $pullPolicy = tpl $pullPolicy $.root -}} +{{- end -}} +name: {{ $containerName }} +image: {{ include "cos-common.image" (dict "image" $vals.image "name" .name "root" .root) }} +imagePullPolicy: {{ $pullPolicy }} +{{- with $vals.command }} +command: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.args }} +args: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.workingDir }} +workingDir: {{ tpl . $.root }} +{{ end }} +{{- with $vals.env }} +env: +{{ include "cos-common.renderList" (dict "list" . "root" $.root) | nindent 2 }} +{{ end }} +{{- with $vals.envFrom }} +envFrom: +{{ include "cos-common.renderList" (dict "list" . "root" $.root) | nindent 2 }} +{{ end }} +{{- with $vals.ports }} + {{- $ports := list }} + {{- range . }} + {{- $normalized := include "cos-common.normalizePortMap" (dict "root" $.root "port" .) | fromJson }} + {{- $ports = append $ports $normalized }} + {{- end }} +ports: +{{ $ports | toYaml | nindent 2 }} +{{ end }} +{{- with $probes.liveness }} +livenessProbe: +{{ include "cos-common.normalizeProbe" (dict "root" $.root "probe" .) | nindent 2 }} +{{ end }} +{{- with $probes.readiness }} +readinessProbe: +{{ include "cos-common.normalizeProbe" (dict "root" $.root "probe" .) | nindent 2 }} +{{ end }} +{{- with $probes.startup }} +startupProbe: +{{ include "cos-common.normalizeProbe" (dict "root" $.root "probe" .) | nindent 2 }} +{{ end }} +{{- with $vals.lifecycle }} +lifecycle: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- $volumeMounts := list }} +{{- with $vals.volumeMounts }} +{{- $volumeMounts = concat $volumeMounts . }} +{{ end }} +{{- with $vals.additionalVolumeMounts }} +{{- $volumeMounts = concat $volumeMounts . }} +{{ end }} +{{- if $tlsConfigs }} + {{- range $app, $cfg := $tlsConfigs }} + {{- if or (not $cfg.mountToContainer) (eq $cfg.mountToContainer $containerName) }} + {{- $volumeMounts = concat $volumeMounts (list (dict "name" (printf "certs-%s" $app) "mountPath" $cfg.mountPath)) }} + {{- end }} + {{- end }} +{{- end }} +{{- if gt (len $volumeMounts) 0 }} +volumeMounts: +{{ tpl (toYaml $volumeMounts) $.root | nindent 2 }} +{{ end }} +{{- with $vals.securityContext }} +securityContext: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.resources }} +resources: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{ end }} + +{{/* +Render additional containers (sidecars/extra). +Merges sidecars and additionalContainers, lets you inherit mounts from another container, +adds TLS mounts, normalizes ports/probes, and strips helper-only keys before render. +*/}} +{{- define "cos-common.additionalContainers" -}} +{{- $vals := .values -}} +{{- $tlsConfigs := default (dict) .tlsConfigs -}} +{{- $containers := list -}} +{{- with $vals.sidecars }} +{{- $containers = concat $containers . -}} +{{ end }} +{{- with $vals.additionalContainers }} +{{- $containers = concat $containers . -}} +{{ end }} +{{- $rendered := list -}} +{{- range $containers }} + {{- $c := deepCopy . -}} + {{- $name := default "" $c.name -}} + {{- $volumeMounts := list -}} + {{- if $c.inheritVolumeMountsFrom }} + {{- /* copy mounts from another named container section (e.g., main.daphne) */ -}} + {{- $inheritFrom := tpl (toString $c.inheritVolumeMountsFrom) $.root -}} + {{- $src := index $vals $inheritFrom -}} + {{- if kindIs "map" $src }} + {{- with (default (list) $src.volumeMounts) }} + {{- $volumeMounts = concat $volumeMounts . -}} + {{- end }} + {{- with (default dict $src.resources).volumeMounts }} + {{- $volumeMounts = concat $volumeMounts . -}} + {{- end }} + {{- end }} + {{- end }} + {{- /* remove helper-only key so it doesn't reach k8s */ -}} + {{- $_ := unset $c "inheritVolumeMountsFrom" -}} + {{- $volumeMounts = concat $volumeMounts (default (list) $c.volumeMounts) -}} + {{- if $tlsConfigs }} + {{- range $app, $cfg := $tlsConfigs }} + {{- if or (not $cfg.mountToContainer) (eq $cfg.mountToContainer $name) }} + {{- $volumeMounts = concat $volumeMounts (list (dict "name" (printf "certs-%s" $app) "mountPath" $cfg.mountPath)) }} + {{- end }} + {{- end }} + {{- end }} + {{- if gt (len $volumeMounts) 0 }} + {{- $_ := set $c "volumeMounts" $volumeMounts -}} + {{- end }} + {{- if $c.ports }} + {{- $ports := list }} + {{- range $c.ports }} + {{- $ports = append $ports (include "cos-common.normalizePortMap" (dict "root" $.root "port" .) | fromJson) }} + {{- end }} + {{- $_ := set $c "ports" $ports -}} + {{- end }} + {{- /* normalize probes (tpl and cast numeric ports to ints) */ -}} + {{- $probeKeys := list "readinessProbe" "livenessProbe" "startupProbe" -}} + {{- range $probeKeys }} + {{- $probe := index $c . }} + {{- if $probe }} + {{- $_ := set $c . (include "cos-common.normalizeProbe" (dict "root" $.root "probe" $probe) | fromYaml) }} + {{- end }} + {{- end }} + {{- $rendered = append $rendered $c -}} +{{- end }} +{{- if gt (len $rendered) 0 }} +{{ tpl (toYaml $rendered) $.root }} +{{- end }} +{{ end }} + +{{/* +Render init containers. +*/}} +{{- define "cos-common.initContainers" -}} +{{- $vals := .values -}} +{{- $tlsConfigs := default (dict) .tlsConfigs -}} +{{- $items := list -}} +{{- with $vals.initContainers }} +{{- $items = concat $items . -}} +{{ end }} +{{- with $vals.additionalInitContainers }} +{{- $items = concat $items . -}} +{{ end }} +{{- if $tlsConfigs }} + {{- $tlsVolumeMounts := list -}} + {{- range $app, $cfg := $tlsConfigs }} + {{- /* Mount cert files from the shared secret so the copy init container can stage them. */ -}} + {{- $tlsFiles := merge (dict) (default dict $cfg.files) (default dict $cfg.base64Files) }} + {{- $tlsVolumeMounts = concat $tlsVolumeMounts (list (dict "name" (printf "certs-%s" $app) "mountPath" $cfg.mountPath)) }} + {{- range $key := keys $tlsFiles }} + {{- $tlsVolumeMounts = concat $tlsVolumeMounts (list (dict "name" "secret" "mountPath" (printf "/certs/%s/%s" $app $key) "subPath" (printf "certs-%s-%s" $app $key) "readOnly" true)) }} + {{- end }} + {{- end }} + {{- $owner := default "www-data:www-data" (get (default dict $vals.enabledInitContainersCertificate) "userCertsOwner") }} + {{- $script := include "cos-common.tlsCopyScript" (dict "configs" $tlsConfigs "owner" $owner) | trim }} + {{- $pullPolicy := default "IfNotPresent" $vals.image.pullPolicy -}} + {{- if and (kindIs "string" $pullPolicy) $.root -}} + {{- $pullPolicy = tpl $pullPolicy $.root -}} + {{- end -}} + {{- $tlsContainer := dict + "name" "certificates" + "image" (include "cos-common.image" (dict "image" $vals.image "name" .name "root" .root)) + "imagePullPolicy" $pullPolicy + "command" (list "/bin/sh" "-c" $script) + "volumeMounts" $tlsVolumeMounts + -}} + {{- $items = concat (list $tlsContainer) $items -}} +{{- end }} +{{- if gt (len $items) 0 }} +initContainers: +{{ tpl (toYaml $items) $.root | nindent 2 }} +{{ end }} +{{ end }} + +{{/* +Resolve the claim name for persistence (component-level or per-volume). +*/}} +{{- define "cos-common.persistenceClaimName" -}} +{{- $root := .root -}} +{{- $vals := default dict .values -}} +{{- $pvc := default dict .persistence -}} +{{- $componentName := default "" .name -}} +{{- $volumeName := default "" .volumeName -}} +{{- if and $volumeName (kindIs "string" $volumeName) -}} + {{- $volumeName = tpl $volumeName $root -}} +{{- end -}} +{{- if $pvc.existingClaim -}} +{{- tpl $pvc.existingClaim $root -}} +{{- else -}} + {{- if and $volumeName (ne $volumeName "") -}} + {{- if $componentName -}} + {{- if ne $componentName $volumeName -}} + {{- $componentName = printf "%s-%s" $componentName $volumeName -}} + {{- end -}} + {{- else -}} + {{- $componentName = $volumeName -}} + {{- end -}} + {{- end -}} + {{- $fullnameOverride := coalesce $pvc.name $pvc.fullnameOverride $vals.fullnameOverride -}} + {{- include "cos-common.fullname" (dict "root" $root "name" $componentName "values" (dict "fullnameOverride" $fullnameOverride)) -}} +{{- end -}} +{{- end }} + +{{/* +Render volumes (including additionalVolumes). +*/}} +{{- define "cos-common.volumes" -}} +{{- $vals := .values -}} +{{- $tlsConfigs := default (dict) .tlsConfigs -}} +{{- $items := list -}} +{{- with $vals.volumes }} +{{- $items = concat $items . -}} +{{ end }} +{{- with $vals.additionalVolumes }} +{{- $items = concat $items . -}} +{{ end }} +{{- $processed := list -}} +{{- range $items }} + {{- $volume := omit . "persistence" -}} + {{- $volName := default "" .name -}} + {{- $persistence := default dict .persistence -}} + {{- if and $persistence (default false $persistence.enabled) -}} + {{- if not $volName -}} + {{- fail (printf "component %s volume requires name when persistence.enabled=true" $.name) -}} + {{- end -}} + {{- if hasKey . "emptyDir" -}} + {{- fail (printf "component %s volume %s cannot use emptyDir when persistence.enabled=true" $.name $volName) -}} + {{- end -}} + {{- $claimName := include "cos-common.persistenceClaimName" (dict "root" $.root "name" $.name "values" $vals "persistence" $persistence "volumeName" $volName) -}} + {{- $volume = dict "name" $volName "persistentVolumeClaim" (dict "claimName" $claimName) -}} + {{- end -}} + {{- $processed = concat $processed (list $volume) -}} +{{- end }} +{{- $items = $processed -}} +{{- $volumeNames := dict -}} +{{- range $items }} + {{- if .name }}{{- $_ := set $volumeNames .name true }}{{- end }} +{{- end }} +{{- if $tlsConfigs }} + {{- $sec := default dict $vals.secret -}} + {{- $secretOverride := coalesce $sec.name $sec.fullnameOverride $vals.fullnameOverride -}} + {{- $secretName := include "cos-common.fullname" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $secretOverride)) -}} + {{- if not (hasKey $volumeNames "secret") }} + {{- $items = concat $items (list (dict "name" "secret" "secret" (dict "secretName" $secretName))) }} + {{- $_ := set $volumeNames "secret" true }} + {{- end }} + {{- range $app, $cfg := $tlsConfigs }} + {{- $volName := printf "certs-%s" $app }} + {{- if not (hasKey $volumeNames $volName) }} + {{- $items = concat $items (list (dict "name" $volName "emptyDir" (dict))) }} + {{- $_ := set $volumeNames $volName true }} + {{- end }} + {{- end }} +{{- end }} +{{- if gt (len $items) 0 }} +volumes: +{{ tpl (toYaml $items) $.root | nindent 2 }} +{{ end }} +{{ end }} + +{{/* +Shared pod spec bits. +*/}} +{{- define "cos-common.podSpec" -}} +{{- $vals := .values -}} +{{- $tlsConfigs := (include "cos-common.enabledInitContainersCertificate" (dict "root" $.root "values" $vals)) | fromYaml }} +{{- with $vals.serviceAccountName }} +serviceAccountName: {{ tpl . $.root }} +{{ end }} +{{- with $vals.automountServiceAccountToken }} +automountServiceAccountToken: {{ . }} +{{ end }} +{{- with $vals.hostNetwork }} +hostNetwork: {{ . }} +{{ end }} +{{- with $vals.hostPID }} +hostPID: {{ . }} +{{ end }} +{{- with $vals.hostIPC }} +hostIPC: {{ . }} +{{ end }} +{{- with $vals.dnsPolicy }} +dnsPolicy: {{ . }} +{{ end }} +{{- with $vals.dnsConfig }} +dnsConfig: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.hostAliases }} +hostAliases: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.schedulerName }} +schedulerName: {{ . }} +{{ end }} +{{- with $vals.terminationGracePeriodSeconds }} +terminationGracePeriodSeconds: {{ . }} +{{ end }} +{{- with $vals.restartPolicy }} +restartPolicy: {{ . }} +{{ end }} +{{- with $vals.imagePullSecrets }} +imagePullSecrets: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.podSecurityContext }} +securityContext: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.priorityClassName }} +priorityClassName: {{ tpl . $.root }} +{{ end }} +{{- with $vals.runtimeClassName }} +runtimeClassName: {{ tpl . $.root }} +{{ end }} +{{- with $vals.shareProcessNamespace }} +shareProcessNamespace: {{ . }} +{{ end }} +{{- with $vals.enableServiceLinks }} +enableServiceLinks: {{ . }} +{{ end }} +{{- with $vals.preemptionPolicy }} +preemptionPolicy: {{ . }} +{{ end }} +{{- with $vals.topologySpreadConstraints }} +topologySpreadConstraints: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- /* Merge the base affinity with additionalAffinities (later entries win). */ -}} +{{- $affinity := dict -}} +{{- if $vals.affinity }} + {{- $affinity = merge $affinity $vals.affinity -}} +{{- end }} +{{- range $extra := default list $vals.additionalAffinities }} + {{- if $extra }} + {{- $affinity = merge $affinity $extra -}} + {{- end }} +{{- end }} +{{- with $affinity }} +affinity: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{- end }} +{{- with $vals.nodeSelector }} +nodeSelector: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.tolerations }} +tolerations: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{- with $vals.readinessGates }} +readinessGates: +{{ tpl (toYaml .) $.root | nindent 2 }} +{{ end }} +{{ include "cos-common.initContainers" (dict "root" .root "name" .name "values" $vals "tlsConfigs" $tlsConfigs) }} +containers: + - {{- include "cos-common.mainContainer" (dict "root" .root "name" .name "values" $vals "tlsConfigs" $tlsConfigs) | nindent 4 }} +{{- with (include "cos-common.additionalContainers" (dict "root" .root "name" .name "values" $vals "tlsConfigs" $tlsConfigs)) }} +{{ . | nindent 2 }} +{{ end }} +{{ include "cos-common.volumes" (dict "root" .root "name" .name "values" $vals "tlsConfigs" $tlsConfigs) }} +{{ end }} + +{{/* +Resolve a name for additional resources (configmaps/secrets) with either name or fullnameOverride. +*/}} +{{- define "cos-common.resolveAdditionalName" -}} +{{- $fullnameOverride := .fullnameOverride -}} +{{- $name := .name -}} +{{- $prefix := default "" .prefix -}} +{{- $error := default "additional entry must have either name or fullnameOverride" .error -}} +{{- if $fullnameOverride }} +{{ $fullnameOverride }} +{{- else if $name }} +{{ printf "%s%s" $prefix $name }} +{{- else }} +{{- fail $error -}} +{{- end }} +{{- end }} + +{{/* +Generic renderer for additional resources that share enable/name/label/annotation patterns. +Callers provide a renderer template name for the resource body. +*/}} +{{- define "cos-common.renderAdditionalResources" -}} +{{- $root := .root -}} +{{- $component := .component -}} +{{- $values := default dict .values -}} +{{- $items := default list .items -}} +{{- $namePrefix := default "" .namePrefix -}} +{{- $error := default "additional entry must have either name or fullnameOverride" .error -}} +{{- $renderer := .renderer -}} +{{- range $item := $items }} + {{- $itemEnabled := or (not (hasKey $item "enabled")) (eq (toString $item.enabled) "true") -}} + {{- if $itemEnabled }} + {{- $name := include "cos-common.resolveAdditionalName" (dict "fullnameOverride" $item.fullnameOverride "name" $item.name "prefix" $namePrefix "error" $error) }} + {{- $labels := merge (dict) (default (dict) $values.labels) (default (dict) $item.labels) -}} + {{- $annotations := include "cos-common.annotations" (dict "values" $values "resource" $item.annotations) | fromJson -}} + {{- include $renderer (dict + "root" $root + "component" $component + "values" $values + "item" $item + "name" $name + "labels" $labels + "annotations" $annotations + "fromOverride" (and (hasKey $item "fullnameOverride") $item.fullnameOverride) + ) + }} + {{- end }} +{{- end }} +{{- end }} + +{{/* +Renderer for additional ConfigMaps (metadata + data blocks). +*/}} +{{- define "cos-common.additionalConfigMapResource" -}} +{{- $root := .root -}} +{{- $name := .name -}} +{{- $labels := .labels -}} +{{- $annotations := .annotations -}} +{{- $item := default dict .item -}} +{{- $data := default dict $item.data -}} +{{- $binaryData := default dict $item.binaryData -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + {{- include "cos-common.metadata" + (dict "root" $root "name" .component "values" + (dict "fullnameOverride" $name "labels" $labels "annotations" $annotations) + ) | nindent 2 + }} + {{- with $item.ownerReferences }} + ownerReferences: + {{- tpl (toYaml .) $root | nindent 4 }} + {{- end }} + +data: +{{- if eq (len $data) 0 }} + {} +{{- else if $item.tpl }} + {{- $renderedData := dict }} + {{- range $key, $value := $data }} + {{- if kindIs "string" $value }} + {{- $_ := set $renderedData $key (tpl $value $root) }} + {{- else }} + {{- $_ := set $renderedData $key $value }} + {{- end }} + {{- end }} +{{ toYaml $renderedData | nindent 2 }} +{{- else }} +{{ toYaml $data | nindent 2 }} +{{- end }} + +{{- if gt (len $binaryData) 0 }} +binaryData: +{{ toYaml $binaryData | nindent 2 }} +{{- end }} + +{{- with $item.immutable }} +immutable: {{ . }} +{{- end }} +{{ end }} + + +{{/* +Renderer for additional Secrets (metadata + data blocks). +*/}} +{{- define "cos-common.additionalSecretResource" -}} +{{- $root := .root -}} +{{- $name := .name -}} +{{- $labels := .labels -}} +{{- $annotations := .annotations -}} +{{- $item := default dict .item -}} +{{- $dataBlock := include "cos-common.buildSecretData" (dict "src" $item "root" $root) }} +--- +apiVersion: v1 +kind: Secret +metadata: + {{- include "cos-common.metadata" + (dict "root" $root "name" .component "values" + (dict "fullnameOverride" $name "labels" $labels "annotations" $annotations) + ) | nindent 2 + }} + {{- with $item.ownerReferences }} + ownerReferences: + {{- tpl (toYaml .) $root | nindent 4 }} + {{- end }} +type: {{ default "Opaque" $item.type }} +{{ $dataBlock | nindent 0 }} +{{- with $item.immutable }} +immutable: {{ . }} +{{- end }} +{{ end }} + + +{{/* +Renderer for additional NetworkPolicies. +*/}} +{{- define "cos-common.additionalNetworkPolicyResource" -}} +{{- $root := .root -}} +{{- $component := .component -}} +{{- $item := default dict .item -}} +{{- if not $item.name }} + {{- fail (printf "component %s.additionalNetworkPolicies entry requires name" $component) -}} +{{- end }} +{{- $name := .name -}} +{{- if not .fromOverride }} + {{- $name = include "cos-common.trim63" $name -}} +{{- end }} +{{- $fullnameOverride := coalesce $item.fullnameOverride $name -}} +{{- $itemNetworkPolicy := merge (dict "enabled" true) (omit $item "labels" "annotations" "fullnameOverride" "enabled" "name") -}} +{{- $itemValues := dict "labels" .labels "annotations" .annotations "fullnameOverride" $fullnameOverride "networkPolicy" $itemNetworkPolicy -}} +{{- include "cos-common.networkpolicy.single" (dict "root" $root "name" $component "values" $itemValues) }} +{{- end }} + +{{/* +Renderer for additional Certificates. +*/}} +{{- define "cos-common.additionalCertificateResource" -}} +{{- $root := .root -}} +{{- $component := .component -}} +{{- $item := default dict .item -}} +{{- if and (not $item.secretName) (not $item.name) }} + {{- fail (printf "component %s.additionalCertificates entry requires name or secretName" $component) }} +{{- end }} +{{- if not $item.issuerRef }} + {{- fail (printf "component %s.additionalCertificates[%s] missing issuerRef" $component (default $item.name $item.secretName)) }} +{{- end }} +{{- $generatedName := .name -}} +{{- $secretName := default $item.secretName $generatedName -}} +{{- if not .fromOverride }} + {{- $generatedName = include "cos-common.trim63" $generatedName -}} +{{- end }} +{{- if not $item.secretName }} + {{- $secretName = include "cos-common.trim63" $secretName -}} +{{- end }} +{{- $spec := (include "cos-common.buildCertSpec" + (dict "src" $item "secretName" $secretName) + ) | fromYaml +}} +{{- include "cos-common.renderCertificate" (dict + "root" $root + "name" $component + "fullnameOverride" (default $generatedName $item.fullnameOverride) + "labels" .labels + "annotations" .annotations + "spec" $spec +) }} +{{- end }} + +{{/* +Render the shared Job spec body so Job and CronJob stay in sync. +*/}} +{{- define "cos-common.jobSpec" -}} +{{- $root := .root -}} +{{- $name := .name -}} +{{- $vals := .values -}} +{{- $suspend := .suspend -}} +{{- $includeManualSelector := default false .includeManualSelector -}} +{{- with $vals.parallelism }} +parallelism: {{ . }} +{{- end }} +{{- with $vals.completions }} +completions: {{ . }} +{{- end }} +{{- with $vals.backoffLimit }} +backoffLimit: {{ . }} +{{- end }} +{{- with $vals.activeDeadlineSeconds }} +activeDeadlineSeconds: {{ . }} +{{- end }} +{{- with $vals.ttlSecondsAfterFinished }} +ttlSecondsAfterFinished: {{ . }} +{{- end }} +{{- with $vals.completionMode }} +completionMode: {{ . }} +{{- end }} +{{- with $suspend }} +suspend: {{ . }} +{{- end }} +{{- with $vals.selector }} +selector: +{{ tpl (toYaml .) $root | nindent 2 }} +{{- end }} +{{- if $includeManualSelector }} +{{- with $vals.manualSelector }} +manualSelector: {{ . }} +{{- end }} +{{- end }} +{{- with $vals.podFailurePolicy }} +podFailurePolicy: +{{ tpl (toYaml .) $root | nindent 2 }} +{{- end }} +template: + metadata: + {{- include "cos-common.podMetadata" (dict "root" $root "name" $name "values" $vals) | nindent 4 }} + spec: + {{- include "cos-common.podSpec" (dict "root" $root "name" $name "values" $vals) | nindent 4 }} +{{- end }} diff --git a/cos-common/templates/_hpa.tpl b/cos-common/templates/_hpa.tpl new file mode 100644 index 00000000..a0410429 --- /dev/null +++ b/cos-common/templates/_hpa.tpl @@ -0,0 +1,41 @@ +{{/* Render an HPA for the component when hpa.enabled is true. */}} +{{- define "cos-common.hpa" -}} +{{- $vals := default dict .values -}} +{{- $hpa := default dict $vals.hpa -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $render := and $componentEnabled (default false $hpa.enabled) -}} +{{- if $render }} +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $hpa.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $hpa.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $hpa.name $hpa.fullnameOverride $vals.fullnameOverride -}} +{{- $scaleTargetDefaults := dict "apiVersion" "apps/v1" "kind" "Deployment" -}} +{{- $scaleTarget := merge $scaleTargetDefaults (default (dict) $hpa.scaleTargetRef) -}} +{{- $targetName := coalesce $hpa.scaleTargetName $scaleTarget.name (include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals)) -}} +{{- $metrics := $hpa.metrics -}} +{{- if not $metrics }} +{{- $metrics = list (dict "type" "Resource" "resource" (dict "name" "cpu" "target" (dict "type" "Utilization" "averageUtilization" 70))) -}} +{{ end }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} +spec: + {{- /* Default target: the Deployment generated by this component unless overridden. */}} + scaleTargetRef: + apiVersion: {{ default "apps/v1" $scaleTarget.apiVersion }} + kind: {{ default "Deployment" $scaleTarget.kind }} + name: {{ $targetName }} + minReplicas: {{ default 1 $hpa.minReplicas }} + maxReplicas: {{ default 1 $hpa.maxReplicas }} + {{- /* Provide a sane CPU target if none supplied to avoid schema errors. */}} + metrics: + {{- range $metric := $metrics }} + - {{ tpl (toYaml $metric) $.root | nindent 6 }} + {{ end }} + {{- with $hpa.behavior }} + behavior: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_ingress.tpl b/cos-common/templates/_ingress.tpl new file mode 100644 index 00000000..f211f111 --- /dev/null +++ b/cos-common/templates/_ingress.tpl @@ -0,0 +1,210 @@ +{{/* Render an Ingress for the component with optional default backend and multiple host rules. */}} +{{- define "cos-common.ingress" -}} +{{- $vals := default dict .values -}} +{{- $ing := default dict $vals.ingress -}} +{{- /* Only render when the component itself and ingress feature are enabled. */ -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $render := and $componentEnabled (default false $ing.enabled) -}} +{{- if $render }} +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $ing.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $ing.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $ing.name $ing.fullnameOverride $vals.fullnameOverride -}} +{{- $maintenance := default dict .root.Values.maintenance -}} +{{- $maintenanceEnabled := default false $maintenance.enabled -}} +{{- $maintenanceServiceName := include "cos-common.fullname" (dict "root" .root "name" "maintenance" "values" $maintenance) -}} +{{- $defaultServiceName := include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals) -}} +{{- $serviceName := $defaultServiceName -}} +{{- /* Swap backend to the maintenance service when maintenance mode is toggled on. */ -}} +{{- if $maintenanceEnabled }} + {{- $serviceName = coalesce $ing.serviceName $ing.backendServiceName $maintenanceServiceName }} +{{- else }} + {{- $serviceName = coalesce $ing.serviceName $ing.backendServiceName $defaultServiceName }} +{{- end }} +{{- $defaultServicePort := $ing.servicePort -}} +{{- if $maintenanceEnabled }} + {{- $defaultServicePort = coalesce $ing.servicePort (default nil $maintenance.service.externalPort) (default nil $maintenance.servicePort) }} +{{- end }} +{{- $defaultServicePort = coalesce $defaultServicePort $vals.http.externalPort }} +{{- $hostsInput := default (list) $ing.hosts -}} +{{- $rules := default (list) $ing.rules -}} +{{- $hosts := list -}} +{{- /* When hosts are provided as primary/secondary lists, expand each rule for the correct host set. */ -}} +{{- $rulesMode := and (kindIs "map" $hostsInput) (or (hasKey $hostsInput "primary") (hasKey $hostsInput "secondary")) (gt (len $rules) 0) -}} +{{- if $rulesMode }} + {{- $primaryHosts := default (list) $hostsInput.primary -}} + {{- $secondaryHosts := default (list) $hostsInput.secondary -}} + {{- $normalizedRules := list -}} + {{- range $ruleEntry := $rules }} + {{- $ruleName := "" -}} + {{- $rule := dict -}} + {{- if and (kindIs "map" $ruleEntry) (hasKey $ruleEntry "name") }} + {{- $ruleName = $ruleEntry.name }} + {{- $rule = $ruleEntry }} + {{- else if and (kindIs "map" $ruleEntry) (eq (len $ruleEntry) 1) }} + {{- range $k, $v := $ruleEntry }} + {{- $ruleName = $k }} + {{- $rule = $v }} + {{- end }} + {{- else }} + {{- $rule = $ruleEntry }} + {{- $ruleName = default "" $rule.name }} + {{- end }} + {{- if default true $rule.enabled }} + {{- if and $ruleName (not $rule.name) }} + {{- $_ := set $rule "name" $ruleName }} + {{- end }} + {{- $normalizedRules = append $normalizedRules $rule }} + {{- end }} + {{- end }} + {{- range $host := $primaryHosts }} + {{- $paths := list }} + {{- range $rule := $normalizedRules }} + {{- /* includeForPrimaryHost/SecondaryHost flags let a rule target only one host group. */ -}} + {{- $include := $rule.includeForPrimaryHost }} + {{- if eq $include nil }}{{- $include = true }}{{- end }} + {{- if $include }} + {{- $rulePaths := default (list) $rule.paths }} + {{- if not $rulePaths }} + {{- fail (printf "component %s.ingress.rules entry requires paths" $.name) }} + {{- end }} + {{- $rulePathType := default "ImplementationSpecific" $rule.pathType }} + {{- $svc := default (dict) $rule.service }} + {{- $ruleServiceName := coalesce $rule.serviceName $svc.name $svc.serviceName }} + {{- $ruleServicePort := coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort }} + {{- range $p := $rulePaths }} + {{- $pathVal := "" }} + {{- $pathType := $rulePathType }} + {{- $pathServiceName := $ruleServiceName }} + {{- $pathPort := $ruleServicePort }} + {{- if kindIs "map" $p }} + {{- $pathVal = default "" $p.path }} + {{- $pathType = default $pathType $p.pathType }} + {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} + {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} + {{- else }} + {{- $pathVal = $p }} + {{- end }} + {{- $pathVal = default "/" $pathVal }} + {{- $pathPort = coalesce $pathPort $defaultServicePort }} + {{- if not $pathPort }} + {{- fail (printf "component %s.ingress rule %s path %s requires a port" $.name (default "" $rule.name) $pathVal) }} + {{- end }} + {{- $paths = append $paths (dict "path" $pathVal "pathType" $pathType "serviceName" $pathServiceName "port" $pathPort) }} + {{- end }} + {{- end }} + {{- end }} + {{- if gt (len $paths) 0 }} + {{- $hosts = append $hosts (dict "host" $host "paths" $paths) }} + {{- end }} + {{- end }} + {{- range $host := $secondaryHosts }} + {{- $paths := list }} + {{- range $rule := $normalizedRules }} + {{- $include := $rule.includeForSecondaryHost }} + {{- if eq $include nil }}{{- $include = true }}{{- end }} + {{- if $include }} + {{- $rulePaths := default (list) $rule.paths }} + {{- if not $rulePaths }} + {{- fail (printf "component %s.ingress.rules entry requires paths" $.name) }} + {{- end }} + {{- $rulePathType := default "ImplementationSpecific" $rule.pathType }} + {{- $svc := default (dict) $rule.service }} + {{- $ruleServiceName := coalesce $rule.serviceName $svc.name $svc.serviceName }} + {{- $ruleServicePort := coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort }} + {{- range $p := $rulePaths }} + {{- $pathVal := "" }} + {{- $pathType := $rulePathType }} + {{- $pathServiceName := $ruleServiceName }} + {{- $pathPort := $ruleServicePort }} + {{- if kindIs "map" $p }} + {{- $pathVal = default "" $p.path }} + {{- $pathType = default $pathType $p.pathType }} + {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} + {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} + {{- else }} + {{- $pathVal = $p }} + {{- end }} + {{- $pathVal = default "/" $pathVal }} + {{- $pathPort = coalesce $pathPort $defaultServicePort }} + {{- if not $pathPort }} + {{- fail (printf "component %s.ingress rule %s path %s requires a port" $.name (default "" $rule.name) $pathVal) }} + {{- end }} + {{- $paths = append $paths (dict "path" $pathVal "pathType" $pathType "serviceName" $pathServiceName "port" $pathPort) }} + {{- end }} + {{- end }} + {{- end }} + {{- if gt (len $paths) 0 }} + {{- $hosts = append $hosts (dict "host" $host "paths" $paths) }} + {{- end }} + {{- end }} +{{- else }} + {{- $hosts = $hostsInput }} +{{- end }} +{{- $backend := default (dict) $ing.defaultBackend -}} +{{- $hasHosts := gt (len $hosts) 0 -}} +{{- $hasBackend := gt (len $backend) 0 -}} +{{- if and (not $hasHosts) (not $hasBackend) }} +{{- fail (printf "component %s.ingress must define hosts or defaultBackend" .name) }} +{{ end }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} +spec: + {{- with $ing.ingressClassName }} + ingressClassName: {{ . }} + {{ end }} + {{- if $hasBackend }} + defaultBackend: + {{- if $backend.service }} + service: + {{ tpl (toYaml $backend.service) $.root | nindent 6 }} + {{- else }} + service: + name: {{ default $serviceName $backend.serviceName }} + port: + {{- $backendPort := coalesce $backend.port $backend.servicePort $defaultServicePort }} + {{- if not $backendPort }} + {{- fail (printf "component %s.ingress.defaultBackend.port is required" .name) }} + {{ end }} + {{ include "cos-common.renderServicePort" (dict "root" $.root "port" $backendPort) | nindent 8 }} + {{ end }} + {{ end }} + {{- with $ing.tls }} + {{- /* Pass through TLS entries as-is to let callers manage secrets and hosts. */}} + tls: + {{- range . }} + - {{ tpl (toYaml .) $.root | nindent 6 }} + {{ end }} + {{ end }} + {{- if $hasHosts }} + rules: + {{- range $host := $hosts }} + - {{- if $host.host }} + host: {{ tpl (toString $host.host) $.root }} + {{ end }} + http: + paths: + {{- $paths := default (list) $host.paths }} + {{- if not $paths }} + {{- fail (printf "component %s.ingress.hosts[].paths must be defined" $.name) }} + {{ end }} + {{- range $path := $paths }} + - path: {{ default "/" $path.path }} + pathType: {{ default "ImplementationSpecific" $path.pathType }} + backend: + service: + {{- $svcName := default $serviceName $path.serviceName }} + name: {{ tpl (toString $svcName) $.root }} + port: + {{- $port := default $defaultServicePort $path.port }} + {{- if not $port }} + {{- fail (printf "component %s.ingress path requires port" $.name) }} + {{ end }} + {{ include "cos-common.renderServicePort" (dict "root" $.root "port" $port) | nindent 18 }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_job.tpl b/cos-common/templates/_job.tpl new file mode 100644 index 00000000..26c75d48 --- /dev/null +++ b/cos-common/templates/_job.tpl @@ -0,0 +1,18 @@ +{{/* Render a one-shot Job for the component, leveraging the shared pod spec. */}} +{{- define "cos-common.job" -}} +{{- $vals := default dict .values -}} +{{- $enabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if $enabled }} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "isWorkload" true) | fromJson -}} +--- +apiVersion: batch/v1 +kind: Job +metadata: + {{- /* Pass workload annotations through so pods inherit the same metadata. */}} + {{- $metadataVals := merge (dict) $vals (dict "annotations" $annotations) -}} + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" $metadataVals) | nindent 2 }} +spec: + {{- /* jobSpec helper keeps CronJob and Job behavior consistent. */}} + {{- include "cos-common.jobSpec" (dict "root" .root "name" .name "values" $vals "suspend" $vals.suspend "includeManualSelector" true) | nindent 2 }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_networkpolicy.tpl b/cos-common/templates/_networkpolicy.tpl new file mode 100644 index 00000000..21921a7c --- /dev/null +++ b/cos-common/templates/_networkpolicy.tpl @@ -0,0 +1,112 @@ +{{/* Render a NetworkPolicy for the component plus any additionalNetworkPolicies entries. */}} +{{- define "cos-common.networkpolicy.single" -}} +{{- $vals := default dict .values -}} +{{- $np := default dict $vals.networkPolicy -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $render := and $componentEnabled (default false $np.enabled) -}} +{{- if $render }} +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $np.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $np.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $np.name $np.fullnameOverride $vals.fullnameOverride -}} +{{- /* Prefer ingressRules/egressRules if set; fall back to extra* for backward compatibility. */ -}} +{{- $ingressRules := default (list) $np.ingressRules -}} +{{- if eq (len $ingressRules) 0 }} + {{- $ingressRules = default (list) $np.extraIngressRules }} +{{- end }} +{{- $egressRules := default (list) $np.egressRules -}} +{{- if eq (len $egressRules) 0 }} + {{- $egressRules = default (list) $np.extraEgressRules }} +{{- end }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} +spec: + podSelector: + {{- if $np.podSelector }} + {{- tpl (toYaml $np.podSelector) $.root | nindent 4 }} + {{- else }} + {{- $selector := include "cos-common.selectorLabels" . | fromYaml }} + {{- /* componentScoped=false: drop component label so one policy can cover all components. */ -}} + {{- $componentScoped := true }} + {{- if hasKey $np "componentScoped" }} + {{- $componentScoped = $np.componentScoped }} + {{- end }} + {{- $componentScoped = toString $componentScoped }} + {{- if eq $componentScoped "false" }} + {{- $_ := unset $selector "app.kubernetes.io/component" }} + {{- end }} + matchLabels: + {{- toYaml $selector | nindent 6 }} + {{- end }} + policyTypes: + - Ingress + {{- if or (default false $np.allowEgress) (gt (len $egressRules) 0) }} + - Egress + {{ end }} + ingress: + {{- if gt (len $ingressRules) 0 }} + {{- /* Normalize each ingress rule and any port maps inside it. */ -}} + {{- range $rule := $ingressRules }} + {{- $rendered := tpl (toYaml $rule) $.root | fromYaml }} + {{- if hasKey $rendered "ports" }} + {{- $ports := list }} + {{- range $rendered.ports }} + {{- $ports = append $ports (include "cos-common.normalizePortMap" (dict "root" $.root "port" .) | fromJson) }} + {{- end }} + {{- $_ := set $rendered "ports" $ports }} + {{- end }} + - {{ $rendered | toYaml | nindent 6 }} + {{ end }} + {{- else }} + {{- /* Default rule: allow traffic from the release namespace if no rules provided. */}} + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .root.Release.Namespace }} + {{ end }} + {{- if or (default false $np.allowEgress) (gt (len $egressRules) 0) }} + egress: + {{- if gt (len $egressRules) 0 }} + {{- range $rule := $egressRules }} + {{- $rendered := tpl (toYaml $rule) $.root | fromYaml }} + {{- if hasKey $rendered "ports" }} + {{- $ports := list }} + {{- range $rendered.ports }} + {{- $ports = append $ports (include "cos-common.normalizePortMap" (dict "root" $.root "port" .) | fromJson) }} + {{- end }} + {{- $_ := set $rendered "ports" $ports }} + {{- end }} + - {{ $rendered | toYaml | nindent 6 }} + {{ end }} + {{ end }} + {{- if default false $np.allowEgress }} + - {} + {{ end }} + {{ end }} +{{ end }} +{{ end }} + +{{- define "cos-common.networkpolicy" -}} +{{- $vals := default dict .values -}} +{{- $root := .root -}} +{{- $name := .name -}} +{{- include "cos-common.networkpolicy.single" . }} +{{- $additional := default list $vals.additionalNetworkPolicies -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if $componentEnabled }} + {{- $releaseName := include "cos-common.releaseName" (dict "root" $root) -}} + {{- $componentName := include "cos-common.componentName" (dict "root" $root "name" $name) -}} + {{- include "cos-common.renderAdditionalResources" (dict + "root" $root + "component" $name + "values" $vals + "items" $additional + "namePrefix" (printf "%s-%s-" $releaseName $componentName) + "error" (printf "component %s.additionalNetworkPolicies entry requires name" $name) + "renderer" "cos-common.additionalNetworkPolicyResource" + ) + }} +{{- end }} +{{ end }} diff --git a/cos-common/templates/_pdb.tpl b/cos-common/templates/_pdb.tpl new file mode 100644 index 00000000..9de7872c --- /dev/null +++ b/cos-common/templates/_pdb.tpl @@ -0,0 +1,31 @@ +{{/* Render a PodDisruptionBudget when pdb.enabled is true. */}} +{{- define "cos-common.pdb" -}} +{{- $vals := default dict .values -}} +{{- $pdb := default dict $vals.pdb -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $render := and $componentEnabled (default false $pdb.enabled) -}} +{{- if $render }} +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $pdb.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $pdb.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $pdb.name $pdb.fullnameOverride $vals.fullnameOverride -}} +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} +spec: + selector: + matchLabels: + {{- include "cos-common.selectorLabels" . | nindent 6 }} + {{- /* Choose either minAvailable or maxUnavailable based on caller input. */}} + {{- with $pdb.minAvailable }} + minAvailable: {{ . }} + {{ end }} + {{- with $pdb.maxUnavailable }} + maxUnavailable: {{ . }} + {{ end }} + {{- with $pdb.unhealthyPodEvictionPolicy }} + unhealthyPodEvictionPolicy: {{ . }} + {{ end }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_pvc.tpl b/cos-common/templates/_pvc.tpl new file mode 100644 index 00000000..d7760f59 --- /dev/null +++ b/cos-common/templates/_pvc.tpl @@ -0,0 +1,111 @@ +{{/* Render a PersistentVolumeClaim for components that opt in. */}} +{{- define "cos-common.pvc" -}} +{{- $vals := default dict .values -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if and $componentEnabled $vals }} + {{- $persistences := list -}} + {{- $componentPersistence := default dict $vals.persistence -}} + {{- if default false $componentPersistence.enabled }} + {{- /* Primary persistence block applies when persistence.enabled=true at component root. */}} + {{- $persistences = concat $persistences (list (dict "persistence" $componentPersistence "volumeName" "")) -}} + {{- end }} + + {{- $volumeSets := list (default (list) $vals.volumes) (default (list) $vals.additionalVolumes) -}} + {{- range $volumeSets }} + {{- range . }} + {{- $p := default dict .persistence -}} + {{- if and $p (default false $p.enabled) }} + {{- $volName := default "" .name -}} + {{- if not $volName -}} + {{- fail (printf "component %s volume requires name when persistence.enabled=true" $.name) -}} + {{- end -}} + {{- if hasKey . "emptyDir" -}} + {{- fail (printf "component %s volume %s cannot use emptyDir when persistence.enabled=true" $.name $volName) -}} + {{- end -}} + {{- /* Collect persistence blocks from volume definitions as well. */}} + {{- $persistences = concat $persistences (list (dict "persistence" $p "volumeName" $volName)) -}} + {{- end -}} + {{- end }} + {{- end }} + + {{- $claims := dict -}} + {{- $claimOrder := list -}} + {{- range $persistences }} + {{- $pvc := default dict .persistence -}} + {{- if and (default false $pvc.enabled) (not $pvc.existingClaim) }} + {{- $claimName := include "cos-common.persistenceClaimName" (dict "root" $.root "name" $.name "values" $vals "persistence" $pvc "volumeName" .volumeName) -}} + {{- if hasKey $claims $claimName -}} + {{- $existing := index $claims $claimName -}} + {{- if ne (toYaml $existing.persistence) (toYaml $pvc) -}} + {{- fail (printf "component %s defines persistence for claim %s more than once with conflicting options" $.name $claimName) -}} + {{- end -}} + {{- else -}} + {{- /* Remember order to keep rendered manifests deterministic. */}} + {{- $_ := set $claims $claimName (dict "persistence" $pvc "volumeName" .volumeName) -}} + {{- $claimOrder = concat $claimOrder (list $claimName) -}} + {{- end -}} + {{- end -}} + {{- end }} + + {{- range $claimOrder }} + {{- $spec := index $claims . -}} + {{- $pvc := $spec.persistence -}} + {{- $volumeName := $spec.volumeName -}} + {{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $pvc.labels) -}} + {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $pvc.annotations) | fromJson -}} + {{- $accessModes := default (list) $pvc.accessModes -}} + {{- if eq (len $accessModes) 0 -}} + {{- if $volumeName -}} + {{- fail (printf "component %s volume %s persistence.accessModes must be set when persistence.enabled=true and no existingClaim" $.name $volumeName) -}} + {{- else -}} + {{- fail (printf "component %s.persistence.accessModes must be set when persistence.enabled=true" $.name) -}} + {{- end -}} + {{- end -}} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- include "cos-common.metadata" + (dict "root" $.root "name" $.name "values" + (dict "fullnameOverride" . "labels" $labels "annotations" $annotations) + ) | nindent 2 + }} +spec: + accessModes: + {{- range $accessModes }} + - {{ . | quote }} + {{- end }} + resources: + {{- if $pvc.resources }} + {{- tpl (toYaml $pvc.resources) $.root | nindent 4 }} + {{- else }} + requests: + storage: {{ default "1Gi" $pvc.size | quote }} + {{- end }} + {{- with $pvc.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} + {{- with $pvc.volumeMode }} + volumeMode: {{ . }} + {{- end }} + {{- with $pvc.volumeName }} + volumeName: {{ . | quote }} + {{- end }} + {{- with $pvc.selector }} + selector: +{{ tpl (toYaml .) $.root | nindent 4 }} + {{- end }} + {{- with $pvc.dataSource }} + dataSource: +{{ tpl (toYaml .) $.root | nindent 4 }} + {{- end }} + {{- with $pvc.dataSourceRef }} + dataSourceRef: +{{ tpl (toYaml .) $.root | nindent 4 }} + {{- end }} + {{- with $pvc.volumeAttributesClassName }} + volumeAttributesClassName: {{ . | quote }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/cos-common/templates/_secret.tpl b/cos-common/templates/_secret.tpl new file mode 100644 index 00000000..542fa1c3 --- /dev/null +++ b/cos-common/templates/_secret.tpl @@ -0,0 +1,130 @@ +{{/* ============================================================================ + Helper: cos-common.buildSecretData + Adds automatic base64 encoding for all .data values and merges in TLS cert files when requested. + ============================================================================ */}} +{{- define "cos-common.buildSecretData" -}} +{{- $src := .src -}} +{{- $root := .root -}} + +{{- $data := default dict $src.data -}} +{{- $stringData := default dict $src.stringData -}} + +{{- $tls := default dict $root.Values.tls -}} + +{{/* TLS merge: optionally pull in files/base64Files from tls.* into this Secret. */}} +{{- if and (default false $src.includeTls) (default false $tls.enabled) }} + {{- range $app, $tlsCfg := omit $tls "enabled" }} + {{- if and $tlsCfg $tlsCfg.enabled }} + {{- with $tlsCfg.files }} + {{- range $key, $value := . }} + {{- $_ := set $data (printf "certs-%s-%s" $app $key) (b64enc $value) }} + {{- end }} + {{- end }} + {{- with $tlsCfg.base64Files }} + {{- range $key, $value := . }} + {{- $_ := set $data (printf "certs-%s-%s" $app $key) (nospace $value) }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} + +{{/* ============================================================================ + AUTO BASE64 FOR .data (stringData is left as-is for K8s to encode) + ============================================================================ */}} +{{- $encoded := dict }} +{{- range $k, $v := $data }} + {{- if kindIs "string" $v }} + {{- $rendered := tpl $v $root }} + {{- $_ := set $encoded $k (b64enc $rendered) }} + {{- else }} + {{- $_ := set $encoded $k (b64enc (toString $v)) }} + {{- end }} +{{- end }} + +{{/* ============================================================================ + YAML OUTPUT + ============================================================================ */}} +{{- if gt (len $encoded) 0 }} +data: +{{ toYaml $encoded | nindent 2 }} +{{- end }} + +{{- if gt (len $stringData) 0 }} +stringData: +{{ tpl (toYaml $stringData) $root | nindent 2 }} +{{- end }} + +{{- end }} + + + +{{/* ============================================================================ + Main Secret + Additional Secrets + ============================================================================ */}} +{{- define "cos-common.secret" -}} + +{{- $vals := default dict .values -}} +{{- $sec := default dict $vals.secret -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $initCertCfg := include "cos-common.initCertConfig" (dict "values" $vals) | fromYaml -}} +{{- $secEnabled := or (default false $sec.enabled) (and (not (hasKey $sec "enabled")) (default false $initCertCfg.enabled)) -}} +{{- $render := and $componentEnabled $secEnabled -}} +{{- $_ := set $sec "includeTls" (or (default false $sec.includeTls) (default false $initCertCfg.enabled)) -}} + +{{/* ============================================================================ + MAIN SECRET + ============================================================================ */}} +{{- if $render }} + +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $sec.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $sec.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $sec.name $sec.fullnameOverride $vals.fullnameOverride -}} + +{{- $dataBlock := include "cos-common.buildSecretData" (dict "src" $sec "root" .root) }} + +--- +apiVersion: v1 +kind: Secret +metadata: + {{- include "cos-common.metadata" + (dict "root" .root "name" .name "values" + (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations) + ) | nindent 2 + }} + {{- with $sec.ownerReferences }} + ownerReferences: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{- end }} +type: {{ default "Opaque" $sec.type }} +{{ $dataBlock | nindent 0 }} +{{- with $sec.immutable }} +immutable: {{ . }} +{{- end }} + +{{- end }} + + + +{{/* ============================================================================ + ADDITIONAL SECRETS + ============================================================================ */}} +{{- if $componentEnabled }} + +{{- $root := .root -}} +{{- $baseName := include "cos-common.fullname" (dict "root" $root "name" .name "values" $vals) | trim -}} + +{{- include "cos-common.renderAdditionalResources" (dict + "root" $root + "component" .name + "values" $vals + "items" $vals.additionalSecrets + "namePrefix" (printf "%s-" $baseName) + "error" "additionalSecrets entry must have either name or fullnameOverride" + "renderer" "cos-common.additionalSecretResource" + ) +}} + +{{- end }} + +{{- end }} diff --git a/cos-common/templates/_service.tpl b/cos-common/templates/_service.tpl new file mode 100644 index 00000000..df0ff2e8 --- /dev/null +++ b/cos-common/templates/_service.tpl @@ -0,0 +1,80 @@ +{{/* Render a Service for the component with templated ports and optional extra services. */}} +{{- define "cos-common.service" -}} +{{- $vals := default dict .values -}} +{{- $svc := default dict $vals.service -}} +{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- $svcEnabled := and $componentEnabled (default true $svc.enabled) -}} +{{- if $svcEnabled }} +{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $svc.labels) -}} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $svc.annotations) | fromJson -}} +{{- $fullnameOverride := coalesce $svc.name $svc.fullnameOverride $vals.fullnameOverride -}} +--- +apiVersion: v1 +kind: Service +metadata: + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} +spec: + {{- $svcType := default "ClusterIP" $svc.type -}} + {{- /* Allow templated service type (e.g., switch to LoadBalancer per env). */ -}} + {{- if and (kindIs "string" $svcType) $.root }}{{- $svcType = tpl $svcType $.root -}}{{- end }} + type: {{ $svcType }} + {{- with $svc.clusterIP }} + clusterIP: {{ . }} + {{ end }} + {{- with $svc.clusterIPs }} + clusterIPs: + {{- range . }} + - {{ . }} + {{ end }} + {{ end }} + {{- with $svc.ipFamilies }} + ipFamilies: + {{- range . }} + - {{ . }} + {{ end }} + {{ end }} + {{- with $svc.ipFamilyPolicy }} + ipFamilyPolicy: {{ . }} + {{ end }} + {{- with $svc.externalIPs }} + externalIPs: + {{- range . }} + - {{ . }} + {{ end }} + {{ end }} + {{- with $svc.loadBalancerIP }} + loadBalancerIP: {{ . }} + {{ end }} + {{- with $svc.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range . }} + - {{ . }} + {{ end }} + {{ end }} + {{- with $svc.externalTrafficPolicy }} + externalTrafficPolicy: {{ . }} + {{ end }} + {{- with $svc.sessionAffinity }} + sessionAffinity: {{ . }} + {{ end }} + {{- with $svc.sessionAffinityConfig }} + sessionAffinityConfig: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} + {{- with $svc.publishNotReadyAddresses }} + publishNotReadyAddresses: {{ . }} + {{ end }} + selector: + {{- include "cos-common.selectorLabels" . | nindent 4 }} + ports: + {{- $ports := default (list) $svc.ports }} + {{- if not $ports }} + {{- fail (printf "component %s.service.ports must be defined" .name) }} + {{ end }} + {{- range $port := $ports }} + {{- /* Normalize templated port fields so numbers stay numbers for k8s. */ -}} + {{- $normalized := include "cos-common.normalizePortMap" (dict "root" $.root "port" $port) | fromJson }} + - {{ $normalized | toYaml | nindent 6 }} + {{ end }} +{{ end }} +{{ end }} diff --git a/cos-common/templates/_statefulset.tpl b/cos-common/templates/_statefulset.tpl new file mode 100644 index 00000000..9b926904 --- /dev/null +++ b/cos-common/templates/_statefulset.tpl @@ -0,0 +1,55 @@ +{{/* Render a StatefulSet for the component, reusing the shared pod spec helpers. */}} +{{- define "cos-common.statefulset" -}} +{{- $vals := default dict .values -}} +{{- $enabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} +{{- if $enabled }} +{{- $annotations := include "cos-common.annotations" (dict "values" $vals "isWorkload" true) | fromJson -}} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + {{- /* Reuse metadata helper so StatefulSet and its pods share labels/annotations. */}} + {{- $metadataVals := merge (dict) $vals (dict "annotations" $annotations) -}} + {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" $metadataVals) | nindent 2 }} +spec: + {{- /* StatefulSet needs a headless service name; default to the component fullname. */}} + serviceName: {{ default (include "cos-common.fullname" .) $vals.serviceName }} + replicas: {{ default 1 $vals.replicas }} + {{- with $vals.revisionHistoryLimit }} + revisionHistoryLimit: {{ . }} + {{ end }} + {{- with $vals.minReadySeconds }} + minReadySeconds: {{ . }} + {{ end }} + {{- with $vals.podManagementPolicy }} + podManagementPolicy: {{ . }} + {{ end }} + {{- with $vals.updateStrategy }} + updateStrategy: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} + {{- with $vals.ordinals }} + ordinals: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} + selector: + matchLabels: + {{- include "cos-common.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- include "cos-common.podMetadata" . | nindent 6 }} + spec: + {{- include "cos-common.podSpec" . | nindent 6 }} + {{- with $vals.volumeClaimTemplates }} + volumeClaimTemplates: + {{- range $vct := . }} + {{- /* Allow templated volume claims so each replica gets its own PVC. */}} + - {{ tpl (toYaml $vct) $.root | nindent 6 }} + {{ end }} + {{ end }} + {{- with $vals.persistentVolumeClaimRetentionPolicy }} + persistentVolumeClaimRetentionPolicy: + {{- tpl (toYaml .) $.root | nindent 4 }} + {{ end }} +{{ end }} +{{ end }} diff --git a/cos-common/values.schema.json b/cos-common/values.schema.json new file mode 100644 index 00000000..753df811 --- /dev/null +++ b/cos-common/values.schema.json @@ -0,0 +1,1711 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/component" + }, + "properties": { + "nameOverride": { + "type": "string", + "description": "Override the chart name for labels and names." + }, + "fullnameOverride": { + "type": "string", + "description": "Override the full release name for labels and names." + }, + "global": { + "type": "object", + "description": "Arbitrary shared values; not rendered directly by the library.", + "additionalProperties": true + }, + "tls": { + "$ref": "#/definitions/tls" + }, + "maintenance": { + "$ref": "#/definitions/maintenance" + } + }, + "definitions": { + "stringOrNumber": { + "type": [ + "string", + "number" + ] + }, + "stringMap": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "anyMap": { + "type": "object", + "additionalProperties": true + }, + "port": { + "description": "Service or container port entry.", + "oneOf": [ + { + "type": [ + "integer", + "number", + "string" + ] + }, + { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "appProtocol": { + "type": "string" + }, + "port": { + "$ref": "#/definitions/stringOrNumber" + }, + "targetPort": { + "$ref": "#/definitions/stringOrNumber" + }, + "containerPort": { + "$ref": "#/definitions/stringOrNumber" + }, + "nodePort": { + "$ref": "#/definitions/stringOrNumber" + }, + "hostPort": { + "$ref": "#/definitions/stringOrNumber" + } + } + } + ] + }, + "probe": { + "type": "object", + "additionalProperties": true, + "properties": { + "httpGet": { + "type": "object", + "additionalProperties": true, + "properties": { + "path": { + "type": "string" + }, + "port": { + "$ref": "#/definitions/stringOrNumber" + }, + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + }, + "httpHeaders": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "tcpSocket": { + "type": "object", + "additionalProperties": true, + "properties": { + "port": { + "$ref": "#/definitions/stringOrNumber" + } + } + }, + "grpc": { + "type": "object", + "additionalProperties": true, + "properties": { + "port": { + "$ref": "#/definitions/stringOrNumber" + }, + "service": { + "type": "string" + }, + "authority": { + "type": "string" + } + } + }, + "exec": { + "type": "object", + "additionalProperties": true, + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "initialDelaySeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "timeoutSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "periodSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "successThreshold": { + "type": [ + "integer", + "number", + "string" + ] + }, + "failureThreshold": { + "type": [ + "integer", + "number", + "string" + ] + } + } + }, + "volumeMount": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "mountPath": { + "type": "string" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "mountPropagation": { + "type": "string" + } + } + }, + "container": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "workingDir": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "envFrom": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/port" + } + }, + "resources": { + "type": "object", + "additionalProperties": true + }, + "securityContext": { + "type": "object", + "additionalProperties": true + }, + "volumeMounts": { + "type": "array", + "items": { + "$ref": "#/definitions/volumeMount" + } + }, + "livenessProbe": { + "$ref": "#/definitions/probe" + }, + "readinessProbe": { + "$ref": "#/definitions/probe" + }, + "startupProbe": { + "$ref": "#/definitions/probe" + }, + "lifecycle": { + "type": "object", + "additionalProperties": true + } + } + }, + "persistence": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "existingClaim": { + "type": "string" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "accessModes": { + "type": "array", + "items": { + "type": "string" + } + }, + "size": { + "type": "string", + "description": "Storage request when resources.requests not provided." + }, + "resources": { + "type": "object", + "additionalProperties": true + }, + "storageClass": { + "type": "string" + }, + "volumeMode": { + "type": "string" + }, + "volumeName": { + "type": "string" + }, + "selector": { + "type": "object", + "additionalProperties": true + }, + "dataSource": { + "type": "object", + "additionalProperties": true + }, + "dataSourceRef": { + "type": "object", + "additionalProperties": true + }, + "volumeAttributesClassName": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + } + } + }, + "volume": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "persistence": { + "$ref": "#/definitions/persistence" + } + } + }, + "service": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "type": { + "type": "string" + }, + "clusterIP": { + "type": "string" + }, + "clusterIPs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ipFamilies": { + "type": "array", + "items": { + "type": "string" + } + }, + "ipFamilyPolicy": { + "type": "string" + }, + "externalIPs": { + "type": "array", + "items": { + "type": "string" + } + }, + "loadBalancerIP": { + "type": "string" + }, + "loadBalancerSourceRanges": { + "type": "array", + "items": { + "type": "string" + } + }, + "externalTrafficPolicy": { + "type": "string" + }, + "sessionAffinity": { + "type": "string" + }, + "sessionAffinityConfig": { + "type": "object", + "additionalProperties": true + }, + "publishNotReadyAddresses": { + "type": "boolean" + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/port" + } + } + } + }, + "ingress": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "ingressClassName": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "backendServiceName": { + "type": "string" + }, + "servicePort": { + "$ref": "#/definitions/stringOrNumber" + }, + "defaultBackend": { + "type": "object", + "additionalProperties": true + }, + "hosts": { + "description": "Either legacy hosts[] array or grouped primary/secondary hosts map.", + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "includeForPrimaryHost": { + "type": "boolean" + }, + "includeForSecondaryHost": { + "type": "boolean" + }, + "pathType": { + "type": "string" + }, + "paths": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": true + } + ] + } + }, + "service": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "port": { + "$ref": "#/definitions/stringOrNumber" + }, + "servicePort": { + "$ref": "#/definitions/stringOrNumber" + }, + "externalPort": { + "$ref": "#/definitions/stringOrNumber" + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "secretName": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "configMap": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "tpl": { + "type": "boolean", + "description": "Render .data values through the Helm templating engine." + }, + "data": { + "type": "object", + "additionalProperties": true + }, + "binaryData": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "ownerReferences": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "immutable": { + "type": "boolean" + } + } + }, + "secret": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "type": { + "type": "string" + }, + "includeTls": { + "type": "boolean", + "description": "Merge files from .Values.tls.* into the secret." + }, + "data": { + "type": "object", + "additionalProperties": true + }, + "stringData": { + "type": "object", + "additionalProperties": true + }, + "ownerReferences": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "immutable": { + "type": "boolean" + } + } + }, + "certificate": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "issuerRef": { + "type": "object", + "additionalProperties": true + }, + "commonName": { + "type": "string" + }, + "dnsNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "emailAddresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "duration": { + "type": "string" + }, + "renewBefore": { + "type": "string" + }, + "usages": { + "type": "array", + "items": { + "type": "string" + } + }, + "privateKey": { + "type": "object", + "additionalProperties": true + }, + "subject": { + "type": "object", + "additionalProperties": true + }, + "secretTemplate": { + "type": "object", + "additionalProperties": true + } + } + }, + "hpa": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "scaleTargetRef": { + "type": "object", + "additionalProperties": true + }, + "scaleTargetName": { + "type": "string", + "description": "Override the target resource name; defaults to the component fullname." + }, + "minReplicas": { + "type": [ + "integer", + "number", + "string" + ] + }, + "maxReplicas": { + "type": [ + "integer", + "number", + "string" + ] + }, + "metrics": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "behavior": { + "type": "object", + "additionalProperties": true + } + } + }, + "pdb": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "minAvailable": { + "type": [ + "integer", + "number", + "string" + ] + }, + "maxUnavailable": { + "type": [ + "integer", + "number", + "string" + ] + }, + "unhealthyPodEvictionPolicy": { + "type": "string" + } + } + }, + "networkPolicy": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "podSelector": { + "type": "object", + "additionalProperties": true + }, + "allowEgress": { + "type": "boolean" + }, + "componentScoped": { + "type": "boolean", + "description": "When false, drop the component label from the selector so one policy can cover multiple components." + }, + "ingressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "egressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "extraIngressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "extraEgressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "additionalNetworkPolicy": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "podSelector": { + "type": "object", + "additionalProperties": true + }, + "allowEgress": { + "type": "boolean" + }, + "componentScoped": { + "type": "boolean", + "description": "When false, drop the component label from the selector so one policy can cover multiple components." + }, + "ingressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "egressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "extraIngressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "extraEgressRules": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + }, + "required": [ + "name" + ] + }, + "additionalConfigMap": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "tpl": { + "type": "boolean" + }, + "data": { + "type": "object", + "additionalProperties": true + }, + "binaryData": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "ownerReferences": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "immutable": { + "type": "boolean" + } + } + }, + "additionalSecret": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "type": { + "type": "string" + }, + "includeTls": { + "type": "boolean" + }, + "data": { + "type": "object", + "additionalProperties": true + }, + "stringData": { + "type": "object", + "additionalProperties": true + }, + "ownerReferences": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "immutable": { + "type": "boolean" + } + } + }, + "additionalCertificate": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "issuerRef": { + "type": "object", + "additionalProperties": true + }, + "commonName": { + "type": "string" + }, + "dnsNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "emailAddresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "duration": { + "type": "string" + }, + "renewBefore": { + "type": "string" + }, + "usages": { + "type": "array", + "items": { + "type": "string" + } + }, + "privateKey": { + "type": "object", + "additionalProperties": true + }, + "subject": { + "type": "object", + "additionalProperties": true + }, + "secretTemplate": { + "type": "object", + "additionalProperties": true + } + } + }, + "component": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean", + "description": "Master enable/disable switch for the component." + }, + "fullnameOverride": { + "type": "string" + }, + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + }, + "annotationsWorkloadOnly": { + "type": "boolean", + "description": "If true, apply component annotations only to workload resources." + }, + "workloadAnnotations": { + "$ref": "#/definitions/stringMap", + "description": "Annotations that apply only to workload resources (deployment/statefulset/job/cronjob)." + }, + "podLabels": { + "$ref": "#/definitions/stringMap" + }, + "podAnnotations": { + "$ref": "#/definitions/stringMap" + }, + "image": { + "type": "object", + "additionalProperties": true, + "properties": { + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "digest": { + "type": "string" + }, + "pullPolicy": { + "type": "string" + } + } + }, + "containerName": { + "type": "string" + }, + "command": { + "type": "array", + "items": { + "type": [ + "string", + "number" + ] + } + }, + "args": { + "type": "array", + "items": { + "type": [ + "string", + "number" + ] + } + }, + "workingDir": { + "type": "string" + }, + "env": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "envFrom": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/port" + } + }, + "probes": { + "type": "object", + "additionalProperties": true, + "properties": { + "liveness": { + "$ref": "#/definitions/probe" + }, + "readiness": { + "$ref": "#/definitions/probe" + }, + "startup": { + "$ref": "#/definitions/probe" + } + } + }, + "lifecycle": { + "type": "object", + "additionalProperties": true + }, + "volumeMounts": { + "type": "array", + "items": { + "$ref": "#/definitions/volumeMount" + } + }, + "additionalVolumeMounts": { + "type": "array", + "items": { + "$ref": "#/definitions/volumeMount" + } + }, + "securityContext": { + "type": "object", + "additionalProperties": true + }, + "resources": { + "type": "object", + "additionalProperties": true + }, + "sidecars": { + "type": "array", + "items": { + "$ref": "#/definitions/container" + } + }, + "additionalContainers": { + "type": "array", + "items": { + "$ref": "#/definitions/container" + } + }, + "initContainers": { + "type": "array", + "items": { + "$ref": "#/definitions/container" + } + }, + "additionalInitContainers": { + "type": "array", + "items": { + "$ref": "#/definitions/container" + } + }, + "enabledInitContainersCertificate": { + "description": "Enable TLS copy init container and optionally target a specific main container.", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "mountToContainer": { + "type": "string" + }, + "userCertsOwner": { + "type": "string" + } + } + } + ] + }, + "volumes": { + "type": "array", + "items": { + "$ref": "#/definitions/volume" + } + }, + "additionalVolumes": { + "type": "array", + "items": { + "$ref": "#/definitions/volume" + } + }, + "persistence": { + "$ref": "#/definitions/persistence" + }, + "serviceAccountName": { + "type": "string" + }, + "automountServiceAccountToken": { + "type": "boolean" + }, + "hostNetwork": { + "type": "boolean" + }, + "hostPID": { + "type": "boolean" + }, + "hostIPC": { + "type": "boolean" + }, + "dnsPolicy": { + "type": "string" + }, + "dnsConfig": { + "type": "object", + "additionalProperties": true + }, + "hostAliases": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "schedulerName": { + "type": "string" + }, + "terminationGracePeriodSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "restartPolicy": { + "type": "string" + }, + "imagePullSecrets": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "podSecurityContext": { + "type": "object", + "additionalProperties": true + }, + "priorityClassName": { + "type": "string" + }, + "runtimeClassName": { + "type": "string" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "enableServiceLinks": { + "type": "boolean" + }, + "preemptionPolicy": { + "type": "string" + }, + "topologySpreadConstraints": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "affinity": { + "type": "object", + "additionalProperties": true + }, + "additionalAffinities": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "nodeSelector": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "tolerations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "readinessGates": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "replicas": { + "type": [ + "integer", + "number", + "string" + ] + }, + "revisionHistoryLimit": { + "type": [ + "integer", + "number", + "string" + ] + }, + "minReadySeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "paused": { + "type": "boolean" + }, + "progressDeadlineSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "strategy": { + "type": "object", + "additionalProperties": true + }, + "service": { + "$ref": "#/definitions/service" + }, + "ingress": { + "$ref": "#/definitions/ingress" + }, + "hpa": { + "$ref": "#/definitions/hpa" + }, + "pdb": { + "$ref": "#/definitions/pdb" + }, + "networkPolicy": { + "$ref": "#/definitions/networkPolicy" + }, + "additionalNetworkPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalNetworkPolicy" + } + }, + "configMap": { + "$ref": "#/definitions/configMap" + }, + "additionalConfigMaps": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalConfigMap" + } + }, + "secret": { + "$ref": "#/definitions/secret" + }, + "additionalSecrets": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalSecret" + } + }, + "certificate": { + "$ref": "#/definitions/certificate" + }, + "additionalCertificates": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalCertificate" + } + }, + "serviceName": { + "type": "string", + "description": "Service name used by StatefulSets." + }, + "podManagementPolicy": { + "type": "string" + }, + "updateStrategy": { + "type": "object", + "additionalProperties": true + }, + "ordinals": { + "type": "object", + "additionalProperties": true + }, + "volumeClaimTemplates": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "persistentVolumeClaimRetentionPolicy": { + "type": "object", + "additionalProperties": true + }, + "parallelism": { + "type": [ + "integer", + "number", + "string" + ] + }, + "completions": { + "type": [ + "integer", + "number", + "string" + ] + }, + "backoffLimit": { + "type": [ + "integer", + "number", + "string" + ] + }, + "activeDeadlineSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "ttlSecondsAfterFinished": { + "type": [ + "integer", + "number", + "string" + ] + }, + "completionMode": { + "type": "string" + }, + "suspend": { + "type": "boolean" + }, + "selector": { + "type": "object", + "additionalProperties": true + }, + "manualSelector": { + "type": "boolean" + }, + "podFailurePolicy": { + "type": "object", + "additionalProperties": true + }, + "jobSuspend": { + "type": "boolean", + "description": "CronJob jobTemplate spec.suspend override." + }, + "schedule": { + "type": "string", + "description": "Cron expression required when rendering a CronJob." + }, + "timeZone": { + "type": "string" + }, + "concurrencyPolicy": { + "type": "string" + }, + "startingDeadlineSeconds": { + "type": [ + "integer", + "number", + "string" + ] + }, + "successfulJobsHistoryLimit": { + "type": [ + "integer", + "number", + "string" + ] + }, + "failedJobsHistoryLimit": { + "type": [ + "integer", + "number", + "string" + ] + }, + "jobTemplate": { + "type": "object", + "additionalProperties": true, + "properties": { + "labels": { + "$ref": "#/definitions/stringMap" + }, + "annotations": { + "$ref": "#/definitions/stringMap" + } + } + }, + "http": { + "type": "object", + "additionalProperties": true, + "properties": { + "internalPort": { + "$ref": "#/definitions/stringOrNumber" + }, + "externalPort": { + "$ref": "#/definitions/stringOrNumber" + } + } + } + } + }, + "tls": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "mountPath": { + "type": "string" + }, + "mountToContainer": { + "type": "string" + }, + "userCertsOwner": { + "type": "string" + }, + "files": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "base64Files": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "maintenance": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean", + "description": "If true, ingress targets the maintenance service/port." + }, + "servicePort": { + "$ref": "#/definitions/stringOrNumber" + }, + "service": { + "type": "object", + "additionalProperties": true, + "properties": { + "externalPort": { + "$ref": "#/definitions/stringOrNumber" + } + } + } + } + } + } +} diff --git a/osf-graveyvalet/Chart.yaml b/osf-graveyvalet/Chart.yaml index 4bd99b90..25fd1cc0 100644 --- a/osf-graveyvalet/Chart.yaml +++ b/osf-graveyvalet/Chart.yaml @@ -1,7 +1,9 @@ -apiVersion: v1 +apiVersion: v2 description: A Helm chart for Kubernetes name: osf-gravyvalet -version: 0.1.0 +type: application +version: 1.0.0 +appVersion: "0.1.0" sources: - https://github.com/CenterForOpenScience/osf-gravyvalet/ maintainers: @@ -11,5 +13,10 @@ maintainers: - name: Matt Clark email: mattclark@cos.io url: https://github.com/mattclark -engine: gotpl -tillerVersion: '>=2.7.0' +dependencies: + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ diff --git a/osf-graveyvalet/files/nginx.conf b/osf-graveyvalet/files/nginx.conf new file mode 100644 index 00000000..8accad2c --- /dev/null +++ b/osf-graveyvalet/files/nginx.conf @@ -0,0 +1,122 @@ +user nginx; +worker_processes {{ .Values.nginx.workerCount }}; + +load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; +{{- if .Values.nginx.vts.enabled }} +load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; +load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; +{{- end }} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + access_log /var/log/nginx/access.log main; + + real_ip_header {{ .Values.nginx.realIpHeader }}; + real_ip_recursive {{ .Values.nginx.realIpRecursive }}; + {{- range .Values.nginx.proxySourceRanges }} + set_real_ip_from {{ . }}; + {{- end }} + + {{- if .Values.nginx.vts.enabled }} + geoip_country /etc/nginx/GeoIP.dat; + geoip_city /etc/nginx/GeoLiteCity.dat; + geoip_proxy_recursive on; + {{- range .Values.nginx.proxySourceRanges }} + geoip_proxy {{ . }}; + {{- end }} + + vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; + vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; + {{- end }} + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 620s; + keepalive_requests 10000; + types_hash_max_size 2048; + server_tokens off; + + gzip on; + gzip_proxied any; + gzip_disable "msie6"; + gzip_min_length 1400; + gzip_vary on; + gzip_buffers 4 32k; + gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + brotli on; + brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + {{- if .Values.nginx.vts.enabled }} + server { + listen {{ .Values.nginx.vts.internalPort }}; + server_name _; + + location /healthz { + access_log off; + return 200; + } + + location /nginx_status { + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + } + {{- end }} + + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + keepalive_timeout 620s; + client_max_body_size 25M; + server_name _; + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /healthz { + access_log off; + return 200; + } + + location = /robots.txt { + alias /usr/share/nginx/html/robots.txt; + } + + location ~* ^/static/(.*) { + alias /static/$1; + } + + location / { + # Disable caching of application requests + add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; + add_header Expires "-1"; + add_header Pragma "no-cache"; + + # Mitigate HTTPoxy Vulnerability + # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ + proxy_set_header Proxy ""; + + proxy_buffering off; + proxy_request_buffering off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_pass http://127.0.0.1:{{ .Values.main.http.containers.nginx.externalPort }}; + } + } +} \ No newline at end of file diff --git a/osf-graveyvalet/templates/NOTES.txt b/osf-graveyvalet/templates/NOTES.txt index 7147cede..32e3215c 100644 --- a/osf-graveyvalet/templates/NOTES.txt +++ b/osf-graveyvalet/templates/NOTES.txt @@ -1,17 +1,28 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "osf-gravyvalet.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "osf-gravyvalet.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "osf-gravyvalet.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "osf-gravyvalet.fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +Component fullname: {{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} + +{{- if and .Values.main.ingress.enabled (.Values.main.ingress.hosts) }} +Ingress hosts: +{{- $hosts := list }} +{{- if and (kindIs "map" .Values.main.ingress.hosts) (or (hasKey .Values.main.ingress.hosts "primary") (hasKey .Values.main.ingress.hosts "secondary")) }} + {{- range $h := (default (list) .Values.main.ingress.hosts.primary) }} + {{- $hosts = append $hosts $h }} + {{- end }} + {{- range $h := (default (list) .Values.main.ingress.hosts.secondary) }} + {{- $hosts = append $hosts $h }} + {{- end }} +{{- else }} + {{- range $h := .Values.main.ingress.hosts }} + {{- $hosts = append $hosts $h.host }} + {{- end }} +{{- end }} +{{- range $hosts }} + - {{ . }} +{{- end }} +{{- else }} +Service ports: +{{- range .Values.main.service.ports }} + - {{ .name }}: {{ .port }} +{{- end }} +Port-forward example: +kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} 8080:{{ (index .Values.main.service.ports 0).port }} {{- end }} diff --git a/osf-graveyvalet/templates/_helpers.tpl b/osf-graveyvalet/templates/_helpers.tpl deleted file mode 100644 index 45d877c1..00000000 --- a/osf-graveyvalet/templates/_helpers.tpl +++ /dev/null @@ -1,163 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "osf-gravyvalet.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified postgresql name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.postgresql.fullname" -}} -{{- $name := "postgresql" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified rabbitmq name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.rabbitmq.fullname" -}} -{{- $name := "rabbitmq" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified migration name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.migration.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.migration.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified worker name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.worker.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.worker.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified beat name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-gravyvalet.beat.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.beat.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Overridable deployment annotations -*/}} -{{- define "osf-gravyvalet.deploymentAnnotations" -}} -checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} -{{- end -}} - -{{- define "osf-gravyvalet.environment" -}} -{{- $fullname := include "osf-gravyvalet.fullname" . -}} -{{- range $key, $value := .Values.configEnvs }} -- name: {{ $key }} - valueFrom: - configMapKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- range $key, $value := .Values.secretEnvs }} -- name: {{ $key }} - valueFrom: - secretKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- if and .Values.persistence.enabled .Values.persistence.mountPath }} -- name: gravyvalet_FILESTORE_DIR - value: {{ .Values.persistence.mountPath }} -{{- end }} -{{- end -}} - -{{- define "gravyvalet.certificates.initContainer" -}} -{{- if .Values.tls.enabled }} -- name: certificates - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - {{- range $app, $tls := omit .Values.tls "enabled" }} - {{- if $tls.enabled }} - cp -f /certs/{{ $app }}/* {{ $tls.mountPath }} - chown -R www-data:www-data {{ $tls.mountPath }} - chmod -R 0600 {{ $tls.mountPath }}/* - {{- end }} - {{- end }} - volumeMounts: - {{- range $app, $tls := omit .Values.tls "enabled" }} - {{- if $tls.enabled }} - - name: certs-{{ $app }} - mountPath: {{ $tls.mountPath }} - {{- range $key := keys $tls.files }} - - mountPath: /certs/{{ $app }}/{{ $key }} - name: secret - subPath: certs-{{ $app }}-{{ $key }} - readOnly: true - {{- end }} - {{- end }} - {{- end }} -{{- end }} -{{- end }} - -{{- define "osf-gravyvalet.volumes" -}} -{{- if .Values.tls.enabled }} -{{- range $app, $tls := omit .Values.tls "enabled" }} -{{- if $tls.enabled }} -- name: certs-{{ $app }} - emptyDir: {} -{{- end }} -{{- end }} -{{- end }} -- name: config - configMap: - name: {{ template "osf-gravyvalet.fullname" . }} -- name: secret - secret: - secretName: {{ template "osf-gravyvalet.fullname" . }} -{{- end -}} - -{{- define "gravyvalet.volumeMounts" -}} -{{- if .Values.volumeMounts -}} -{{- toYaml .Values.volumeMounts }} -{{- end -}} -{{- if .Values.tls.enabled }} -{{- range $app, $tls := omit .Values.tls "enabled" }} -{{- if $tls.enabled }} -- name: certs-{{ $app }} - mountPath: {{ $tls.mountPath }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} diff --git a/osf-graveyvalet/templates/beat-deployment.yaml b/osf-graveyvalet/templates/beat-deployment.yaml deleted file mode 100644 index d187c46e..00000000 --- a/osf-graveyvalet/templates/beat-deployment.yaml +++ /dev/null @@ -1,133 +0,0 @@ -{{- if .Values.beat.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "osf-gravyvalet.beat.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.beat.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - component: "{{ .Values.beat.name }}" - release: {{ .Release.Name }} - replicas: 1 - strategy: - type: Recreate - template: - metadata: - labels: - app: {{ template "osf-gravyvalet.name" . }} - component: "{{ .Values.beat.name }}" - release: {{ .Release.Name }} - annotations: - {{- include "osf-gravyvalet.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.beat.additionalAffinities }} - {{- toYaml .Values.beat.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.beat.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.beat.name }}" - {{- else if eq .Values.beat.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.beat.name }}" - {{- end }} - initContainers: - - name: chown - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/bash - - -c - - chown -R www-data:www-data /beat /log - securityContext: - runAsUser: 0 - volumeMounts: - - mountPath: /beat - name: beat - - mountPath: /log - name: log - {{- if .Values.tls.enabled }} - {{- include "gravyvalet.certificates.initContainer" . | nindent 8 }} - {{- end }} - containers: - - name: {{ .Values.beat.name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - set -e - SUFFIX='' - if [ -f /beat/celerybeat-schedule ]; then - SUFFIX='--schedule=/beat/celerybeat-schedule' - fi - python -m celery --app app beat -l debug --pidfile= $SUFFIX - env: - - name: DJANGO_SETTINGS_MODULE - value: app.settings - - name: LOG_PATH - value: /log - {{- include "osf-gravyvalet.environment" . | nindent 12 }} - {{- range $key, $value := .Values.beat.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - # lifecycle: - # postStart: - # exec: - # command: - # - /bin/sh - # - -c - # - chmod o+w /beat - volumeMounts: - - mountPath: /beat - name: beat - - mountPath: /log - name: log - {{- include "gravyvalet.volumeMounts" . | nindent 12 }} - {{- if .Values.beat.volumeMounts }} - {{- toYaml .Values.beat.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.beat.resources }} - resources: - {{- toYaml .Values.beat.resources | nindent 12 }} - {{- end }} - volumes: - {{- include "osf-gravyvalet.volumes" . | nindent 8 }} - # TODO: Remove when djang-celery-beat is configured - - name: beat - {{- if .Values.beat.persistence.enabled }} - persistentVolumeClaim: - claimName: {{ .Values.beat.persistence.existingClaim | default (include "osf-gravyvalet.beat.fullname" .) }} - {{- else }} - emptyDir: {} - {{- end }} - - name: log - emptyDir: {} - {{- if .Values.beat.nodeSelector }} - nodeSelector: - {{- toYaml .Values.beat.nodeSelector | nindent 8 }} - {{- end }} -{{- end -}} diff --git a/osf-graveyvalet/templates/beat-pvc.yaml b/osf-graveyvalet/templates/beat-pvc.yaml deleted file mode 100644 index 55ca00ce..00000000 --- a/osf-graveyvalet/templates/beat-pvc.yaml +++ /dev/null @@ -1,25 +0,0 @@ -{{- /* TODO: Remove when djang-celery-beat is configured */ -}} -{{- if and .Values.beat.enabled .Values.beat.persistence.enabled (not .Values.beat.persistence.existingClaim) -}} -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: {{ template "osf-gravyvalet.beat.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.beat.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - {{- if .Values.beat.persistence.storageClass }} - volume.beta.kubernetes.io/storage-class: {{ .Values.beat.persistence.storageClass | quote }} - {{- else }} - volume.alpha.kubernetes.io/storage-class: default - {{- end }} -spec: - accessModes: - - {{ .Values.beat.persistence.accessMode | quote }} - resources: - requests: - storage: {{ .Values.beat.persistence.size | quote }} -{{- end -}} diff --git a/osf-graveyvalet/templates/beat.yaml b/osf-graveyvalet/templates/beat.yaml new file mode 100644 index 00000000..eb1f6bdc --- /dev/null +++ b/osf-graveyvalet/templates/beat.yaml @@ -0,0 +1,6 @@ +{{- include "cos-common.deployment" (dict "root" . "name" "beat" "values" .Values.beat) }} +{{- include "cos-common.hpa" (dict "root" . "name" "beat" "values" .Values.beat) }} +{{- include "cos-common.pdb" (dict "root" . "name" "beat" "values" .Values.beat) }} +{{- include "cos-common.pvc" (dict "root" . "name" "beat" "values" .Values.beat) }} +{{- include "cos-common.configmap" (dict "root" . "name" "beat" "values" .Values.beat) }} +{{- include "cos-common.secret" (dict "root" . "name" "beat" "values" .Values.beat) }} diff --git a/osf-graveyvalet/templates/certificate-networkpolicy.yaml b/osf-graveyvalet/templates/certificate-networkpolicy.yaml deleted file mode 100644 index 1865f6ac..00000000 --- a/osf-graveyvalet/templates/certificate-networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if (and .Values.networkPolicy.enabled .Values.certificate.enabled) }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: "{{ template "osf-gravyvalet.certificate.fullname" . }}" - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - policyTypes: - - Ingress - podSelector: - matchExpressions: - - {key: acme.cert-manager.io/http01-solver, operator: Exists} - ingress: - - from: [] -{{- end }} diff --git a/osf-graveyvalet/templates/certificate.yaml b/osf-graveyvalet/templates/certificate.yaml deleted file mode 100644 index ca88aa14..00000000 --- a/osf-graveyvalet/templates/certificate.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if and .Values.certificate.enabled .Values.certificate.createCert -}} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "{{ template "osf-gravyvalet.certificate.fullname" . }}" - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - secretName: "{{ template "osf-gravyvalet.certificate.fullname" . }}" - issuerRef: - name: {{ .Values.certificate.issuerRef.name }} - kind: {{ .Values.certificate.issuerRef.kind }} - commonName: {{ .Values.certificate.commonName }} - dnsNames: - {{- range .Values.certificate.dnsNames }} - - {{ . }} - {{- end }} - acme: - config: - - http01: - {{- if hasKey .Values.certificate.acmeConfig.http01 "ingress" }} - ingress: {{ .Values.certificate.acmeConfig.http01.ingress }} - {{- else }} - ingress: {{ template "osf-gravyvalet.fullname" . }} - {{- end }} - domains: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} -{{- end -}} diff --git a/osf-graveyvalet/templates/configmap.yaml b/osf-graveyvalet/templates/configmap.yaml deleted file mode 100644 index 3c2055fa..00000000 --- a/osf-graveyvalet/templates/configmap.yaml +++ /dev/null @@ -1,143 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: - {{- define "osf-gravyvalet.inlineconfigs" }} -nginx.conf: |- - user nginx; - worker_processes {{ .Values.nginx.workerCount }}; - - load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; - {{- if .Values.nginx.vts.enabled }} - load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; - load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; - {{- end }} - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - access_log /var/log/nginx/access.log main; - - real_ip_header {{ .Values.nginx.realIpHeader }}; - real_ip_recursive {{ .Values.nginx.realIpRecursive }}; - {{- range .Values.nginx.proxySourceRanges }} - set_real_ip_from {{ . }}; - {{- end }} - - {{- if .Values.nginx.vts.enabled }} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - {{- range .Values.nginx.proxySourceRanges }} - geoip_proxy {{ . }}; - {{- end }} - - vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; - {{- end }} - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 620s; - keepalive_requests 10000; - types_hash_max_size 2048; - server_tokens off; - - gzip on; - gzip_proxied any; - gzip_disable "msie6"; - gzip_min_length 1400; - gzip_vary on; - gzip_buffers 4 32k; - gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - brotli on; - brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - {{- if .Values.nginx.vts.enabled }} - server { - listen {{ .Values.nginx.vts.internalPort }}; - server_name _; - - location /healthz { - access_log off; - return 200; - } - - location /nginx_status { - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - } - } - {{- end }} - - server { - listen {{ .Values.service.internalPort }}; - keepalive_timeout 620s; - client_max_body_size 25M; - server_name _; - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /healthz { - access_log off; - return 200; - } - - location = /robots.txt { - alias /usr/share/nginx/html/robots.txt; - } - - location ~* ^/static/(.*) { - alias /static/$1; - } - - location / { - # Disable caching of application requests - add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; - add_header Expires "-1"; - add_header Pragma "no-cache"; - - # Mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - proxy_buffering off; - proxy_request_buffering off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; - proxy_pass http://127.0.0.1:{{ .Values.service.externalPort }}; - } - } - } -{{- end -}} - {{- range $key, $value := .Values.configEnvs }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- range $key, $value := merge .Values.configFiles (include "osf-gravyvalet.inlineconfigs" . | fromYaml) ((.Files.Glob "files/*").AsConfig | fromYaml) }} - {{ $key }}: |- - {{- $value | nindent 4 }} - {{- end }} - diff --git a/osf-graveyvalet/templates/deployment.yaml b/osf-graveyvalet/templates/deployment.yaml deleted file mode 100644 index 5e942a51..00000000 --- a/osf-graveyvalet/templates/deployment.yaml +++ /dev/null @@ -1,173 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - replicas: {{ .Values.replicaCount }} - {{- if .Values.strategy }} - strategy: - {{- toYaml .Values.strategy | nindent 4 }} - {{- end }} - template: - metadata: - labels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - annotations: - {{- include "osf-gravyvalet.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - {{- end }} - initContainers: - - name: collectstatic - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - python - - manage.py - - collectstatic - - --noinput - volumeMounts: - - mountPath: /code/static - name: static - {{- if .Values.tls.enabled }} - {{- include "gravyvalet.certificates.initContainer" . | nindent 8 }} - {{- end }} - containers: - - name: nginx - image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" - imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} - command: - - nginx - - -c - - /etc/nginx/nginx.conf - - -g - - daemon off; - ports: - - name: http-internal - containerPort: {{ .Values.service.internalPort }} - readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.internalPort }} - initialDelaySeconds: 10 - volumeMounts: - - name: config - subPath: nginx.conf - mountPath: /etc/nginx/nginx.conf - readOnly: true - - name: config - subPath: robots.txt - mountPath: /usr/share/nginx/html/robots.txt - readOnly: true - - mountPath: /static - name: static - resources: - {{- toYaml .Values.nginx.resources | nindent 12 }} - - name: daphne - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - python -m daphne -b 0.0.0.0 -p {{ .Values.service.externalPort }} app.asgi:application - env: - {{- include "osf-gravyvalet.environment" . | nindent 12 }} - {{- range $key, $value := .Values.daphne.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - ports: - - name: http-external - containerPort: {{ .Values.service.externalPort }} - readinessProbe: - httpGet: - path: /v1/status/ - port: {{ .Values.service.externalPort }} - {{- if .Values.service.probeHost }} - httpHeaders: - - name: host - value: {{ .Values.service.probeHost }} - {{- end }} - volumeMounts: - - name: localcache - mountPath: /tmp/gravyvaletlocalcache - {{- include "gravyvalet.volumeMounts" . | nindent 12 }} - {{- if .Values.daphne.volumeMounts }} - {{- toYaml .Values.daphne.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.persistence.enabled }} - - name: data - mountPath: {{ .Values.persistence.mountPath }} - {{- end }} - resources: - {{- toYaml .Values.daphne.resources | nindent 12 }} - volumes: - {{- include "osf-gravyvalet.volumes" . | nindent 8 }} - - name: static - emptyDir: {} - - name: localcache - emptyDir: {} - - name: data - {{- if .Values.persistence.enabled }} - persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim | default (include "osf-gravyvalet.fullname" .) }} - {{- else }} - emptyDir: {} - {{- end }} - - name: config-volume - configMap: - name: {{ template "osf-gravyvalet.fullname" . }} - - name: secret-volume - secret: - secretName: {{ template "osf-gravyvalet.fullname" . }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - {{- range .Values.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - {{- if hasKey .Values.persistence "storageClass" }} - storageClassName: {{ .Values.persistence.storageClass | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.persistence.size | quote }} - {{- end }} diff --git a/osf-graveyvalet/templates/hpa.yaml b/osf-graveyvalet/templates/hpa.yaml deleted file mode 100644 index d1e49785..00000000 --- a/osf-graveyvalet/templates/hpa.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "osf-gravyvalet.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} - diff --git a/osf-graveyvalet/templates/ingress.yaml b/osf-graveyvalet/templates/ingress.yaml deleted file mode 100644 index facd32bc..00000000 --- a/osf-graveyvalet/templates/ingress.yaml +++ /dev/null @@ -1,49 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "osf-gravyvalet.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- $serviceName := include "osf-gravyvalet.fullname" . -}} - {{- $servicePort := .Values.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls)) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "osf-gravyvalet.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- end -}} -{{- end -}} diff --git a/osf-graveyvalet/templates/main.yaml b/osf-graveyvalet/templates/main.yaml new file mode 100644 index 00000000..31e2b1e5 --- /dev/null +++ b/osf-graveyvalet/templates/main.yaml @@ -0,0 +1,10 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.certificate" (dict "root" . "name" "cert" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.service" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.ingress" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.hpa" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pdb" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pvc" (dict "root" . "name" "" "values" .Values.main) }} diff --git a/osf-graveyvalet/templates/migration-job.yaml b/osf-graveyvalet/templates/migration-job.yaml deleted file mode 100644 index d8e27816..00000000 --- a/osf-graveyvalet/templates/migration-job.yaml +++ /dev/null @@ -1,59 +0,0 @@ -{{- if .Values.migration.enabled -}} -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ template "osf-gravyvalet.migration.fullname" . }}-{{ .Release.Revision }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.migration.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": post-install,post-upgrade -spec: - activeDeadlineSeconds: 900 - template: - metadata: - name: "{{ .Release.Name }}" - labels: - app: {{ template "osf-gravyvalet.name" . }} - component: "{{ .Values.migration.name }}" - release: {{ .Release.Name }} - annotations: - checksum/secret: {{ include (print $.Template.BasePath "/migration-secret.yaml") . | sha256sum }} - spec: - restartPolicy: Never - initContainers: - {{- include "gravyvalet.certificates.initContainer" . | nindent 8 }} - containers: - - name: {{ .Values.migration.name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - python manage.py migrate - env: - {{- include "osf-gravyvalet.environment" . | nindent 12 }} - {{- range $key, $value := .Values.migration.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - volumeMounts: - {{- include "gravyvalet.volumeMounts" . | nindent 12 }} - {{- if .Values.volumeMounts }} - {{- toYaml .Values.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.migration.volumeMounts }} - {{- toYaml .Values.migration.volumeMounts | nindent 12 }} - {{- end }} - volumes: - {{- include "osf-gravyvalet.volumes" . | nindent 8 }} - - name: config-volume - configMap: - name: {{ template "osf-gravyvalet.fullname" . }} - - name: secret-volume - secret: - secretName: {{ template "osf-gravyvalet.fullname" . }} -{{- end -}} diff --git a/osf-graveyvalet/templates/migration-secret.yaml b/osf-graveyvalet/templates/migration-secret.yaml deleted file mode 100644 index 40fdd582..00000000 --- a/osf-graveyvalet/templates/migration-secret.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "osf-gravyvalet.fullname" . }}-migration - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade - "helm.sh/hook-weight": "-5" - "helm.sh/hook-delete-policy": "before-hook-creation" -type: Opaque -data: - {{- range $key, $value := .Values.secretEnvs }} - {{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- range $key, $value := .Values.secretFiles }} - {{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- if .Values.tls.enabled }} - {{- range $app, $tls := omit .Values.tls "enabled" }} - {{- if $tls.enabled }} - {{- range $key, $value := $tls.files }} - certs-{{ $app }}-{{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} diff --git a/osf-graveyvalet/templates/migration.yaml b/osf-graveyvalet/templates/migration.yaml new file mode 100644 index 00000000..57fb96eb --- /dev/null +++ b/osf-graveyvalet/templates/migration.yaml @@ -0,0 +1,3 @@ +{{- include "cos-common.secret" (dict "root" . "name" "migration" "values" .Values.migration) }} +{{- include "cos-common.configmap" (dict "root" . "name" "migration" "values" .Values.migration) }} +{{- include "cos-common.job" (dict "root" . "name" "migration" "values" .Values.migration) }} diff --git a/osf-graveyvalet/templates/networkpolicy.yaml b/osf-graveyvalet/templates/networkpolicy.yaml deleted file mode 100644 index bb844521..00000000 --- a/osf-graveyvalet/templates/networkpolicy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - podSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - ingress: - - ports: - - port: {{ .Values.service.internalPort }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "osf-gravyvalet.fullname" . }}-client: "true" - {{- end }} - {{- if .Values.nginx.vts.enabled }} - - ports: - - port: {{ .Values.nginx.vts.internalPort }} - {{- end }} - egress: {{- toYaml .Values.networkPolicy.egress | nindent 4 }} -{{- end }} diff --git a/osf-graveyvalet/templates/pdb.yaml b/osf-graveyvalet/templates/pdb.yaml deleted file mode 100644 index adbe988e..00000000 --- a/osf-graveyvalet/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "osf-gravyvalet.fullname" . }}" - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/osf-graveyvalet/templates/secret.yaml b/osf-graveyvalet/templates/secret.yaml deleted file mode 100644 index 062f9a86..00000000 --- a/osf-graveyvalet/templates/secret.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: - {{- range $key, $value := .Values.secretEnvs }} - {{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- range $key, $value := .Values.secretFiles }} - {{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- if .Values.tls.enabled }} - {{- range $app, $tls := omit .Values.tls "enabled" }} - {{- if $tls.enabled }} - {{- range $key, $value := $tls.files }} - certs-{{ $app }}-{{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} diff --git a/osf-graveyvalet/templates/service.yaml b/osf-graveyvalet/templates/service.yaml deleted file mode 100644 index 74fe9d80..00000000 --- a/osf-graveyvalet/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "osf-gravyvalet.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "osf-gravyvalet.name" . }} - release: {{ .Release.Name }} diff --git a/osf-graveyvalet/templates/worker-deployment.yaml b/osf-graveyvalet/templates/worker-deployment.yaml deleted file mode 100644 index 47dbb338..00000000 --- a/osf-graveyvalet/templates/worker-deployment.yaml +++ /dev/null @@ -1,91 +0,0 @@ -{{- if .Values.worker.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "osf-gravyvalet.worker.fullname" . }} - labels: - app: {{ template "osf-gravyvalet.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.worker.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - component: "{{ .Values.worker.name }}" - release: {{ .Release.Name }} - replicas: {{ .Values.worker.replicaCount }} - template: - metadata: - labels: - app: {{ template "osf-gravyvalet.name" . }} - component: "{{ .Values.worker.name }}" - release: {{ .Release.Name }} - annotations: - {{- include "osf-gravyvalet.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.worker.additionalAffinities }} - {{- toYaml .Values.worker.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.worker.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.worker.name }}" - {{- else if eq .Values.worker.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-gravyvalet.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.worker.name }}" - {{- end }} - {{- if .Values.tls.enabled }} - initContainers: - {{- include "gravyvalet.certificates.initContainer" . | nindent 8 }} - {{- end }} - containers: - - name: {{ .Values.worker.name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - python -m celery --app app worker \ - --concurrency "{{ .Values.worker.concurrency }}" --loglevel "{{ .Values.worker.logLevel }}" \ - --hostname $POD_NAME --without-gossip -Ofair - {{- if .Values.worker.maxTasksPerChild }} --max-tasks-per-child "{{ .Values.worker.maxTasksPerChild }}"{{- end }} - {{- if .Values.worker.queues }} --queues "{{ .Values.worker.queues }}"{{- end }} - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- include "osf-gravyvalet.environment" . | nindent 12 }} - {{- range $key, $value := .Values.worker.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - volumeMounts: - {{- include "gravyvalet.volumeMounts" . | nindent 12 }} - {{- if .Values.worker.volumeMounts }} - {{- toYaml .Values.worker.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.worker.resources }} - resources: - {{- toYaml .Values.worker.resources | nindent 12 }} - {{- end }} - volumes: - {{- include "osf-gravyvalet.volumes" . | nindent 8 }} -{{- end -}} diff --git a/osf-graveyvalet/templates/worker.yaml b/osf-graveyvalet/templates/worker.yaml new file mode 100644 index 00000000..691e1521 --- /dev/null +++ b/osf-graveyvalet/templates/worker.yaml @@ -0,0 +1,6 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.secret" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.deployment" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.hpa" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.pdb" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "worker" "values" .Values.worker) }} diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 465019d5..8fec5c5a 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -1,40 +1,13 @@ -# Default values for osf-gravyvalet. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 - -image: +### ------- Global or Reusable parts across values.yaml ------- +appImage: repository: quay.io/centerforopenscience/osf-gravyvalet tag: develop pullPolicy: Always -antiAffinity: soft - -# strategy: -# rollingUpdate: -# maxSurge: 25% -# maxUnavailable: 25% -# type: RollingUpdate - -budget: - minAvailable: 0 - nginx: workerCount: 1 - image: - repository: quay.io/centerforopenscience/nginx - tag: latest - pullPolicy: Always resources: {} - # limits: - # cpu: 1 - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi proxySourceRanges: [] - # - 130.211.0.0/22 - # - 35.191.0.0/16 realIpHeader: X-Real-IP realIpRecursive: "off" vts: @@ -43,228 +16,834 @@ nginx: statusZoneSize: 10m defaultFilterKey: "$geoip_country_code country::*" -daphne: - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -horizontalPodAutoscaler: +# TLS client certificate scaffold (disabled by default) +# override-values.yaml +tls: enabled: false - maxReplicas: 3 - targetCPUUtilizationPercentage: 90 + postgresql: + enabled: false + mountPath: /var/www/.postgresql + files: + # Root Certificate + root.crt: |- + + # Root Certificate Revocation List + root.crl: |- + + # Database key + postgresql.key: |- + + # Database certificate + postgresql.crt: |- + + rabbitmq: + enabled: false + mountPath: /var/www/.rabbitmq + files: + worker.key: |- + + worker.pem: |- + + ca-chain.cert.pem: |- -service: - name: http - type: ClusterIP - externalPort: 8000 - internalPort: 80 -ingress: - enabled: false - # Used to create Ingress record (should used with service.type: ClusterIP). - hosts: - - chart-example.local - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - paths: - - / - tls: - # Secrets must be manually created in the namespace. - # - secretName: osf-io-tls - # hosts: - # - chart-example.local - -certificate: - enabled: false - createCert: false - name: cert - # WORKAROUND: Ingress deploy blocked to GLBC due to race condition w/ missing TLS certificate - # - Issue: https://github.com/jetstack/cert-manager/issues/606 - # - PR: https://github.com/kubernetes/ingress-gce/pull/388 - tls: true - # issuerRef: - # name: letsencrypt-prod - # kind: ClusterIssuer - # commonName: example.org - # dnsNames: - # - example.org - # - subdomain.example.org - # acmeConfig: - # http01: {} - # # ingress: '' - # domains: - # - example.org - # - subdomain.example.org +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name + +## =============== MAIN Component =============== +main: + enabled: true + + component: main + + replicas: 1 + http: + containers: + nginx: + internalPort: 80 + externalPort: 8000 + serviceType: ClusterIP + +# ------- Configuration follows for containerName: nginx ------- + image: + repository: quay.io/centerforopenscience/nginx + tag: latest + pullPolicy: Always + + containerName: nginx + + command: + - nginx + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + + env: [] + + envFrom: [] + + probes: + readiness: + httpGet: + path: /healthz + port: "{{ .Values.main.http.containers.nginx.internalPort }}" + initialDelaySeconds: 10 + + ports: + - name: http-internal + containerPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + volumeMounts: + - name: static + mountPath: /static + readOnly: true + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + - name: config + mountPath: /usr/share/nginx/html/robots.txt + subPath: robots.txt + readOnly: true + + additionalVolumeMounts: [] + + resources: {} + +# ------- Init containers ------- + initContainers: + - name: collectstatic + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - python + - manage.py + - collectstatic + - --noinput + volumeMounts: + - name: static + mountPath: /code/static + + additionalInitContainers: [] + + # will add init containers which change permissions for TLS certs and mounts certs to the target container + enabledInitContainersCertificate: + enabled: "{{ .Values.tls.enabled }}" + mountToContainer: daphne + userCertsOwner: www-data:www-data + + +# ------- Additional containers ------- + daphne: + resources: + limits: + cpu: 1 + memory: 512Mi + requests: + cpu: 0 + memory: 256Mi + ephemeral-storage: 10Gi + volumeMounts: + - name: localcache + mountPath: /tmp/gravyvaletlocalcache + #### If Enabled persistence < ---------- + # - name: data + # mountPath: /var/lib/gravyvaletlocaldata/ + + sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) + + additionalContainers: + - name: daphne + inheritVolumeMountsFrom: daphne # <----- gets volume mounts from daphne set of vars above + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/sh + - -c + - "python -m daphne -b 0.0.0.0 -p {{ .Values.main.http.containers.nginx.externalPort }} app.asgi:application" + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "daphne-env") | trim }}' + ports: + - name: http-external + containerPort: "{{ .Values.main.http.containers.nginx.externalPort }}" + readinessProbe: + httpGet: + path: /v1/status/ + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + resources: + limits: + cpu: "{{ .Values.main.daphne.resources.limits.cpu }}" + memory: "{{ .Values.main.daphne.resources.limits.memory }}" + requests: + cpu: "{{ .Values.main.daphne.resources.requests.cpu }}" + memory: "{{ .Values.main.daphne.resources.requests.memory }}" + ephemeral-storage: "{{ index .Values.main.daphne.resources.requests \"ephemeral-storage\" }}" + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: static + emptyDir: {} + - name: localcache + emptyDir: {} + + additionalVolumes: + - name: data + emptyDir: {} + #### If Enabled persistence < ---------- + # -----> Option 1 to create persistent volume + # + # - name: data # PVC name will be: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + # persistence: + # enabled: true + # accessModes: + # - ReadWriteOnce + # size: 100Gi + # storageClass: "" + # existingClaim: "" + + # -----> Option 2 to create persistent volume + # + # - name: data + # persistentVolumeClaim: + # claimName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + # -----> Option 2 to create persistent volume + # + # PVC name will be '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + # + # persistence: + # enabled: true + # accessModes: + # - ReadWriteOnce + # size: 100Gi + # storageClass: "" + # existingClaim: "" + + + # ------- Affinity configuration ------- + affinity: {} + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 1 + # podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave name in main.yaml empty. + + additionalAffinities: [] + # - nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: cloud.google.com/gke-preemptible + # operator: In + # values: + # - "true" + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" + checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + service: + enabled: true + type: "{{ .Values.main.http.containers.nginx.serviceType }}" + ports: + - name: http + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + targetPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + # secondary: + # - chart-example-2.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + paths: + - / + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local + + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + + # additionalCertificates: + # certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + name + # - name: example-org-cert + # enabled: false + # secretName: secret-with-cert # default secret name is certificate name + # commonName: example.org + # dnsNames: + # - example.org + # - submdomain.example.org + # issuerRef: + # name: letsencrypt-prod + # kind: ClusterIssuer + # acmeConfig: + # http01: {} + # # ingress: '' + # domains: + # - example.org + # - subdomain.example.org + + +# ------- HPA configuration ------- + hpa: + enabled: false + minReplicas: "{{ .Values.main.replicas }}" + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + behavior: {} + + +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 1 + + +# ------- Network Policy configuration ------- + networkPolicy: + enabled: false + componentScoped: false # this network policy will be applied to all components + allowEgress: true + ingressRules: + - from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "client") | trim }}': "true" + ports: + - port: "{{ .Values.main.http.containers.nginx.internalPort }}" + - ports: + - port: "{{ .Values.nginx.vts.internalPort }}" + egressRules: + - {} + + additionalNetworkPolicies: + - name: cert-solver + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- ConfigMap configuration ------- +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + configMap: + enabled: true + tpl: true + data: + nginx.conf: | + {{ tpl (.Files.Get "files/nginx.conf") (dict "Values" .Values "root" .) }} + robots.txt: |- + {{ .Files.Get "files/robots.txt" }} + +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalConfigMaps: + - name: common-env + enabled: true + tpl: false + data: {} + - name: daphne-env + enabled: true + tpl: false + data: {} + #### If Enabled persistence < ---------- + # gravyvalet_FILESTORE_DIR: /var/lib/gravyvaletlocaldata/ + # Provide other envs for daphne + + +# ------- Secrets configuration ------- +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: true + includeTls: "{{ .Values.tls.enabled }}" # will include certs from "tls" block in values + data: {} + +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalSecrets: + - name: common-env + enabled: true + includeTls: false + data: {} + + +# =============== WORKER Component =============== worker: enabled: true - name: worker - replicaCount: 1 - antiAffinity: soft + replicas: 1 - budget: - minAvailable: 0 + component: worker + +# ------- Configuration follows for containerName: "{{ .Values.worker.component }}" ------- + image: + repository: "{{ .Values.appImage.repository }}" + tag: "{{ .Values.appImage.tag }}" + pullPolicy: "{{ .Values.appImage.pullPolicy }}" + + containerName: "{{ .Values.worker.component }}" concurrency: 5 logLevel: INFO maxTasksPerChild: 5 - # queues: ... + queues: "" + + command: + - /bin/sh + - -c + - |- + python -m celery --app app worker \ + --concurrency "{{ .Values.worker.concurrency }}" --loglevel "{{ .Values.worker.logLevel }}" \ + --hostname $POD_NAME --without-gossip -Ofair + {{- if .Values.worker.maxTasksPerChild }} --max-tasks-per-child "{{ .Values.worker.maxTasksPerChild }}"{{- end }} + {{- if .Values.worker.queues }} --queues "{{ .Values.worker.queues }}"{{- end }} + + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker-env") | trim }}' + + probes: {} + + volumeMounts: [] + + additionalVolumeMounts: [] resources: {} - #limits: - # cpu: 100m - # memory: 128Mi - #requests: - # cpu: 100m - # memory: 128Mi - - horizontalPodAutoscaler: + +# ------- Init containers ------- + + initContainers: [] + + additionalInitContainers: [] + + # will add init containers which change permissions for TLS certs and mounts certs to the target container + enabledInitContainersCertificate: + enabled: "{{ .Values.tls.enabled }}" + + +# ------- Additional containers ------- + additionalContainers: [] + + sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + +# ------- Affitnity configuration ------- + affinity: {} + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 1 + # podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Values.worker.component }}" + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" + checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" + + +# ------- HPA configuration ------- + hpa: enabled: false + minReplicas: "{{ .Values.worker.replicas }}" maxReplicas: 3 - targetCPUUtilizationPercentage: 90 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + behavior: {} + + +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 1 - env: {} - volumeMounts: [] +# ------- ConfigMap configuration ------- +# configMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker") | trim }}' + configMap: + enabled: false + +# configMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker") | trim }}' + name + additionalConfigMaps: + - name: env + enabled: true + tpl: false + data: {} + +# ------- Secrets configuration ------- +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker") | trim }}' + secret: + enabled: true + includeTls: "{{ .Values.tls.enabled }}" + data: {} + +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "worker") | trim }}' + name + additionalSecrets: + - name: env + enabled: true + includeTls: false + data: {} + + +# ------- Selectors and etc. ------- + + nodeSelector: {} + + tolerations: [] + + +# =============== BEAT Component =============== beat: enabled: true - name: beat - antiAffinity: soft + replicas: 1 + + component: beat + + strategy: + type: Recreate + +# ------- Configuration follows for containerName: "{{ .Values.beat.component }}" ------- + image: + repository: "{{ .Values.appImage.repository }}" + tag: "{{ .Values.appImage.tag }}" + pullPolicy: "{{ .Values.appImage.pullPolicy }}" + + containerName: "{{ .Values.beat.component }}" + + command: + - /bin/sh + - -c + - |- + set -e + SUFFIX='' + if [ -f /beat/celerybeat-schedule ]; then + SUFFIX='--schedule=/beat/celerybeat-schedule' + fi + python -m celery --app app beat -l debug --pidfile= $SUFFIX + + env: + - name: DJANGO_SETTINGS_MODULE + value: app.settings + - name: LOG_PATH + value: /log + + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat-env") | trim }}' + + probes: {} + + volumeMounts: + - mountPath: /beat + name: "{{ .Values.beat.component }}" + - mountPath: /log + name: log + + additionalVolumeMounts: [] resources: {} - # limits: - # cpu: "1" - # memory: 512Mi - # requests: - # cpu: 100m - # memory: 256Mi - - ## Node labels for component pod assignment - ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ - ## - nodeSelector: {} - env: {} +# ------- Init containers ------- + initContainers: + - name: chown + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/bash + - -c + - chown -R www-data:www-data /beat /log + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /beat + name: "{{ .Values.beat.component }}" + - mountPath: /log + name: log + + additionalInitContainers: [] + + # will add init containers which change permissions for TLS certs and mounts certs to the target container + enabledInitContainersCertificate: + enabled: "{{ .Values.tls.enabled }}" + + +# ------- Additional containers ------- + additionalContainers: [] + + sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: log + emptyDir: {} + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + additionalVolumes: + - name: "{{ .Values.beat.component }}" + persistence: + enabled: true + accessModes: + - ReadWriteOnce + size: 4Gi + existingClaim: "" + storageClass: "" + + +# ------- Affitnity configuration ------- + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app.kubernetes.io/name: "{{ .Chart.Name }}" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/component: "{{ .Values.beat.component }}" + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" + checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" + + +# ------- ConfigMap configuration ------- +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat") | trim }}' + configMap: + enabled: false + +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat") | trim }}' + name + additionalConfigMaps: + - name: env + enabled: true + tpl: false + data: {} - volumeMounts: [] - persistence: +# ------- Secrets configuration ------- +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat") | trim }}' + secret: enabled: true - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - # storageClass: ssd - accessMode: ReadWriteOnce - size: 4Gi + includeTls: "{{ .Values.tls.enabled }}" + data: {} + +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "beat") | trim }}' + name + additionalSecrets: + - name: env + enabled: true + includeTls: false + data: {} + + +# ------- Selectors and etc. ------- + nodeSelector: {} + + tolerations: [] + +# =============== MIGRATION Component =============== migration: enabled: true - name: migration - resources: {} - #limits: - # cpu: 100m - # memory: 128Mi - #requests: - # cpu: 100m - # memory: 128Mi + component: migration + +# ------- Configuration follows for containerName: "{{ .Values.migration.component }}" ------- + image: + repository: "{{ .Values.appImage.repository }}" + tag: "{{ .Values.appImage.tag }}" + pullPolicy: "{{ .Values.appImage.pullPolicy }}" + + containerName: "{{ .Values.migration.component }}" + + activeDeadlineSeconds: 900 + + workloadAnnotations: + "helm.sh/hook": post-install,post-upgrade + + restartPolicy: Never - env: {} + command: + - /bin/sh + - -c + - python manage.py migrate + + env: [] + + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration-env") | trim }}' volumeMounts: [] -networkPolicy: - enabled: false - # Allows external access to the pod, otherwise access is restricted to - # clients with the explicit label. - allowExternal: true - egress: {} - # - to: - # - namespaceSelector: {} - # ports: - # - port: 53 # dns - # protocol: TCP - # - port: 53 # dns - # protocol: UDP - # - to: - # - ipBlock: - # cidr: 0.0.0.0/0 - # except: - # - 10.0.0.0/8 - # - 172.16.0.0/12 - # - 192.168.0.0/16 - # ports: - # - port: 80 # http - # protocol: TCP - # - port: 443 # https - # protocol: TCP - -configEnvs: {} - # DEBUG: "" - -configFiles: {} - # Override configmap files here (and delete the {} above), e.g.: - #robots.txt: |- - # User-agent: * - # Disallow: / - -secretEnvs: {} - -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## -persistence: - enabled: false - ## Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 100Gi - - # mountPath: /var/lib/gravyvaletlocaldata/ - -# Client-side TLS -tls: - enabled: false + additionalVolumeMounts: [] - postgresql: + +# ------- Init containers ------- + initContainers: [] + + additionalInitContainers: [] + + # will add init containers which change permissions for TLS certs and mounts certs to the target container + enabledInitContainersCertificate: + enabled: "{{ .Values.tls.enabled }}" + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" + checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" + + +# ------- ConfigMap configuration ------- +# configMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration") | trim }}' + configMap: enabled: false - mountPath: /var/www/.postgresql - files: - # Root Certificate - root.crt: |- - # Root Certificate Revocation List - root.crl: |- - - # Database key - postgresql.key: |- - - # Database certificate - postgresql.crt: |- - - rabbitmq: - enabled: true - mountPath: /var/www/.rabbitmq - files: - worker.key: |- - - worker.pem: |- - - ca-chain.cert.pem: |- +# configMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration") | trim }}' + name + additionalConfigMaps: + - name: env + enabled: true + tpl: false + data: {} + +# ------- Secrets configuration ------- +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration") | trim }}' + secret: + enabled: true + includeTls: "{{ .Values.tls.enabled }}" + data: {} + +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "migration") | trim }}' + name + additionalSecrets: + - name: env + enabled: true + includeTls: false + data: {} From bc764658583205f1b4633c1ebe02a8ef4da1b7da Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Mon, 15 Dec 2025 20:23:00 +0200 Subject: [PATCH 02/11] Refactor ingress tpl, process acmeConfig for certs --- angular-osf/values.yaml | 12 +- cos-common/README.md | 2 +- cos-common/templates/_certificate.tpl | 36 +++- cos-common/templates/_helpers.tpl | 14 +- cos-common/templates/_ingress.tpl | 231 ++++++++++++++++---------- cos-common/values.schema.json | 15 ++ osf-graveyvalet/values.yaml | 8 +- 7 files changed, 220 insertions(+), 98 deletions(-) diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index b6fa7894..b6405826 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -232,12 +232,18 @@ main: commonName: example.org dnsNames: - example.org + acmeConfig: + http01: + ingress: 'example' + domains: + - example.org + - subdomain.example.org # additionalCertificates: - # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name + # # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name # - name: example-org-cert - # enabled: false - # secretName: secret-with-cert + # enabled: true + # secretName: secret-with-cert # default secret name is certificate name # commonName: example.org # dnsNames: # - example.org diff --git a/cos-common/README.md b/cos-common/README.md index 4839ad5a..4bb21b83 100644 --- a/cos-common/README.md +++ b/cos-common/README.md @@ -141,7 +141,7 @@ app: - **HPA**: when `enabled`, `minReplicas`, `maxReplicas`, and `metrics` are required. - **PDB**: when `enabled`, set either `minAvailable` or `maxUnavailable` (not both). - **NetworkPolicy**: defaults to namespace-local ingress allow; egress only if `allowEgress: true` or `extraEgressRules` present. Use `componentScoped: false` to drop the component label when you want one policy to cover multiple components. `additionalNetworkPolicies[]` supported. -- **Certificates**: renders cert-manager `Certificate`; `issuerRef` required when enabled. `additionalCertificates[]` available. +- **Certificates**: renders cert-manager `Certificate`; `issuerRef` required when enabled. `certificate.acmeConfig` maps to `spec.acme.config[]` (defaults `http01.ingress` to the chart fullname when not set). `additionalCertificates[]` available. - **Persistence**: component-level `persistence` or `volumes[].persistence` can auto-create PVCs (unless `existingClaim`); per-volume persistence forbids `emptyDir` and wires the volume to the claim automatically. - **CronJob**: `schedule` is required; job-spec knobs (`parallelism`, `backoffLimit`, `podFailurePolicy`, etc.) live directly under the component block. - **Annotations**: `annotations` apply broadly by default; set `annotationsWorkloadOnly: true` or use `workloadAnnotations` to scope/override annotations on the workload resources only (deployment/statefulset/job/cronjob), e.g., for Helm hooks. diff --git a/cos-common/templates/_certificate.tpl b/cos-common/templates/_certificate.tpl index bbfddf72..4dca3a46 100644 --- a/cos-common/templates/_certificate.tpl +++ b/cos-common/templates/_certificate.tpl @@ -5,9 +5,12 @@ {{- define "cos-common.buildCertSpec" -}} {{- $src := .src -}} {{- $secretName := .secretName -}} +{{- $root := .root -}} +{{- $values := default dict .values -}} {{- $spec := dict "secretName" $secretName -}} {{- $fields := list + "acme" "commonName" "dnsNames" "ipAddresses" @@ -28,6 +31,33 @@ {{- end }} {{- end }} +{{- /* Back-compat convenience: map certificate.acmeConfig -> spec.acme.config[]. */ -}} +{{- if and (not (hasKey $spec "acme")) -}} + {{- with (get $src "acmeConfig") -}} + {{- $acmeCfg := default dict . -}} + {{- $http01 := default dict (get $acmeCfg "http01") -}} + {{- $domains := default list (get $acmeCfg "domains") -}} + + {{- $shouldAddAcme := or (gt (len $domains) 0) (gt (len $http01) 0) -}} + {{- if $shouldAddAcme -}} + + {{- $http01Rendered := dict -}} + {{- if gt (len $http01) 0 -}} + {{- $http01Rendered = merge (dict) $http01 -}} + {{- end -}} + + {{- if not (hasKey $http01Rendered "ingress") -}} + {{- if not $root }} + {{- fail "cos-common.buildCertSpec requires root when certificate.acmeConfig.http01.ingress is not set" -}} + {{- end }} + {{- $_ := set $http01Rendered "ingress" ((include "cos-common.fullname" (dict "root" $root "name" "" "values" $values)) | trim) -}} + {{- end -}} + + {{- $_ := set $spec "acme" (dict "config" (list (dict "http01" $http01Rendered "domains" $domains))) -}} + {{- end -}} + {{- end -}} +{{- end -}} + {{- toYaml $spec -}} {{- end }} @@ -61,6 +91,7 @@ metadata: }} spec: {{- tpl (toYaml $spec) $root | nindent 2 }} +{{- print "\n" -}} {{- end }} @@ -91,7 +122,7 @@ spec: {{- $secretName := default $name $cert.secretName -}} {{- $spec := (include "cos-common.buildCertSpec" - (dict "src" $cert "secretName" $secretName) + (dict "src" $cert "secretName" $secretName "root" .root "values" $vals) ) | fromYaml -}} @@ -118,7 +149,8 @@ spec: {{- $prefix := printf "%s-%s-%s-" $releaseName $chartName $component -}} {{- $items := list -}} {{- range $item := default list $vals.additionalCertificates }} - {{- $items = append $items (merge (dict "name" (default $item.name $item.secretName)) $item) }} + {{- $itemName := default $item.secretName $item.name -}} + {{- $items = append $items (merge $item (dict "name" $itemName)) }} {{- end }} {{- include "cos-common.renderAdditionalResources" (dict "root" $root diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl index 59e70590..ecc20495 100644 --- a/cos-common/templates/_helpers.tpl +++ b/cos-common/templates/_helpers.tpl @@ -757,9 +757,9 @@ Resolve a name for additional resources (configmaps/secrets) with either name or {{- $prefix := default "" .prefix -}} {{- $error := default "additional entry must have either name or fullnameOverride" .error -}} {{- if $fullnameOverride }} -{{ $fullnameOverride }} +{{- $fullnameOverride -}} {{- else if $name }} -{{ printf "%s%s" $prefix $name }} +{{- printf "%s%s" $prefix $name -}} {{- else }} {{- fail $error -}} {{- end }} @@ -916,15 +916,17 @@ Renderer for additional Certificates. {{- fail (printf "component %s.additionalCertificates[%s] missing issuerRef" $component (default $item.name $item.secretName)) }} {{- end }} {{- $generatedName := .name -}} -{{- $secretName := default $item.secretName $generatedName -}} +{{- $secretName := default $generatedName $item.secretName -}} {{- if not .fromOverride }} - {{- $generatedName = include "cos-common.trim63" $generatedName -}} + {{- $generatedName = (include "cos-common.trim63" $generatedName) | trim -}} {{- end }} {{- if not $item.secretName }} - {{- $secretName = include "cos-common.trim63" $secretName -}} + {{- $secretName = (include "cos-common.trim63" $secretName) | trim -}} +{{- else }} + {{- $secretName = $secretName | trim -}} {{- end }} {{- $spec := (include "cos-common.buildCertSpec" - (dict "src" $item "secretName" $secretName) + (dict "src" $item "secretName" $secretName "root" $root "values" .values) ) | fromYaml }} {{- include "cos-common.renderCertificate" (dict diff --git a/cos-common/templates/_ingress.tpl b/cos-common/templates/_ingress.tpl index f211f111..ba337146 100644 --- a/cos-common/templates/_ingress.tpl +++ b/cos-common/templates/_ingress.tpl @@ -1,42 +1,93 @@ -{{/* Render an Ingress for the component with optional default backend and multiple host rules. */}} +{{/* +Render an Ingress for a component. +This helper intentionally supports many input shapes to keep values.yaml flexible +and backward-compatible across services. +*/}} {{- define "cos-common.ingress" -}} + +{{- /* Normalize component values and ingress subtree to always be maps */ -}} {{- $vals := default dict .values -}} {{- $ing := default dict $vals.ingress -}} -{{- /* Only render when the component itself and ingress feature are enabled. */ -}} + +{{- /* Render ingress only if: + 1) the component itself is enabled + 2) ingress feature is explicitly enabled */ -}} {{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} {{- $render := and $componentEnabled (default false $ing.enabled) -}} {{- if $render }} + +{{- /* Merge base component labels with ingress-specific labels */ -}} {{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $ing.labels) -}} + +{{- /* Resolve annotations via shared annotation helper + (supports global + resource-specific annotations) */ -}} {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $ing.annotations) | fromJson -}} + +{{- /* Optional fullname override coming from ingress or component */ -}} {{- $fullnameOverride := coalesce $ing.name $ing.fullnameOverride $vals.fullnameOverride -}} + +{{- /* Maintenance configuration is global and lives at root.Values */ -}} {{- $maintenance := default dict .root.Values.maintenance -}} {{- $maintenanceEnabled := default false $maintenance.enabled -}} + +{{- /* Resolve service names: + - default component service + - maintenance service (when enabled) */ -}} {{- $maintenanceServiceName := include "cos-common.fullname" (dict "root" .root "name" "maintenance" "values" $maintenance) -}} {{- $defaultServiceName := include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals) -}} + +{{- /* Select backend service name depending on maintenance mode */ -}} {{- $serviceName := $defaultServiceName -}} -{{- /* Swap backend to the maintenance service when maintenance mode is toggled on. */ -}} {{- if $maintenanceEnabled }} {{- $serviceName = coalesce $ing.serviceName $ing.backendServiceName $maintenanceServiceName }} {{- else }} {{- $serviceName = coalesce $ing.serviceName $ing.backendServiceName $defaultServiceName }} {{- end }} + +{{- /* Resolve backend service port. + Maintenance mode may override the port as well. */ -}} {{- $defaultServicePort := $ing.servicePort -}} {{- if $maintenanceEnabled }} - {{- $defaultServicePort = coalesce $ing.servicePort (default nil $maintenance.service.externalPort) (default nil $maintenance.servicePort) }} + {{- $defaultServicePort = coalesce + $ing.servicePort + (default nil $maintenance.service.externalPort) + (default nil $maintenance.servicePort) + }} {{- end }} {{- $defaultServicePort = coalesce $defaultServicePort $vals.http.externalPort }} + +{{- /* Hosts and rules input normalization */ -}} {{- $hostsInput := default (list) $ing.hosts -}} {{- $rules := default (list) $ing.rules -}} {{- $hosts := list -}} -{{- /* When hosts are provided as primary/secondary lists, expand each rule for the correct host set. */ -}} -{{- $rulesMode := and (kindIs "map" $hostsInput) (or (hasKey $hostsInput "primary") (hasKey $hostsInput "secondary")) (gt (len $rules) 0) -}} + +{{- /* +Determine whether we are in "rules mode": +- hosts defined as a map with primary/secondary keys +- rules list exists and defines paths/services +*/ -}} +{{- $rulesMode := and + (kindIs "map" $hostsInput) + (or (hasKey $hostsInput "primary") (hasKey $hostsInput "secondary")) + (gt (len $rules) 0) +-}} + {{- if $rulesMode }} + + {{- /* Split hosts into primary and secondary groups */ -}} {{- $primaryHosts := default (list) $hostsInput.primary -}} {{- $secondaryHosts := default (list) $hostsInput.secondary -}} + + {{- /* Normalize rule entries to a consistent structure: + - supports named rules + - supports map shorthand + - filters out disabled rules */ -}} {{- $normalizedRules := list -}} {{- range $ruleEntry := $rules }} {{- $ruleName := "" -}} {{- $rule := dict -}} + + {{- /* Support multiple rule declaration styles */ -}} {{- if and (kindIs "map" $ruleEntry) (hasKey $ruleEntry "name") }} {{- $ruleName = $ruleEntry.name }} {{- $rule = $ruleEntry }} @@ -49,6 +100,8 @@ {{- $rule = $ruleEntry }} {{- $ruleName = default "" $rule.name }} {{- end }} + + {{- /* Only include enabled rules */ -}} {{- if default true $rule.enabled }} {{- if and $ruleName (not $rule.name) }} {{- $_ := set $rule "name" $ruleName }} @@ -56,105 +109,118 @@ {{- $normalizedRules = append $normalizedRules $rule }} {{- end }} {{- end }} + + {{- /* Expand rules for PRIMARY hosts */ -}} {{- range $host := $primaryHosts }} {{- $paths := list }} + {{- range $rule := $normalizedRules }} - {{- /* includeForPrimaryHost/SecondaryHost flags let a rule target only one host group. */ -}} + {{- /* Rules may explicitly opt out of primary hosts */ -}} {{- $include := $rule.includeForPrimaryHost }} {{- if eq $include nil }}{{- $include = true }}{{- end }} + {{- if $include }} - {{- $rulePaths := default (list) $rule.paths }} + {{- /* Validate rule paths */ -}} + {{- $rulePaths := default (list) $rule.paths -}} {{- if not $rulePaths }} {{- fail (printf "component %s.ingress.rules entry requires paths" $.name) }} {{- end }} + + {{- /* Resolve rule-level defaults */ -}} {{- $rulePathType := default "ImplementationSpecific" $rule.pathType }} {{- $svc := default (dict) $rule.service }} - {{- $ruleServiceName := coalesce $rule.serviceName $svc.name $svc.serviceName }} - {{- $ruleServicePort := coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort }} - {{- range $p := $rulePaths }} - {{- $pathVal := "" }} - {{- $pathType := $rulePathType }} - {{- $pathServiceName := $ruleServiceName }} - {{- $pathPort := $ruleServicePort }} - {{- if kindIs "map" $p }} - {{- $pathVal = default "" $p.path }} - {{- $pathType = default $pathType $p.pathType }} - {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} - {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} - {{- else }} - {{- $pathVal = $p }} - {{- end }} - {{- $pathVal = default "/" $pathVal }} - {{- $pathPort = coalesce $pathPort $defaultServicePort }} - {{- if not $pathPort }} - {{- fail (printf "component %s.ingress rule %s path %s requires a port" $.name (default "" $rule.name) $pathVal) }} - {{- end }} - {{- $paths = append $paths (dict "path" $pathVal "pathType" $pathType "serviceName" $pathServiceName "port" $pathPort) }} - {{- end }} - {{- end }} - {{- end }} - {{- if gt (len $paths) 0 }} - {{- $hosts = append $hosts (dict "host" $host "paths" $paths) }} - {{- end }} - {{- end }} - {{- range $host := $secondaryHosts }} - {{- $paths := list }} - {{- range $rule := $normalizedRules }} - {{- $include := $rule.includeForSecondaryHost }} - {{- if eq $include nil }}{{- $include = true }}{{- end }} - {{- if $include }} - {{- $rulePaths := default (list) $rule.paths }} - {{- if not $rulePaths }} - {{- fail (printf "component %s.ingress.rules entry requires paths" $.name) }} + {{- $ruleServiceName := $serviceName }} + {{- $ruleServicePort := $defaultServicePort }} + + {{- /* Rule-level overrides disabled during maintenance */ -}} + {{- if not $maintenanceEnabled }} + {{- $ruleServiceName = coalesce $rule.serviceName $svc.name $svc.serviceName $ruleServiceName }} + {{- $ruleServicePort = coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort $ruleServicePort }} {{- end }} - {{- $rulePathType := default "ImplementationSpecific" $rule.pathType }} - {{- $svc := default (dict) $rule.service }} - {{- $ruleServiceName := coalesce $rule.serviceName $svc.name $svc.serviceName }} - {{- $ruleServicePort := coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort }} + + {{- /* Expand individual paths */ -}} {{- range $p := $rulePaths }} - {{- $pathVal := "" }} - {{- $pathType := $rulePathType }} - {{- $pathServiceName := $ruleServiceName }} - {{- $pathPort := $ruleServicePort }} + {{- $pathVal := "" -}} + {{- $pathType := $rulePathType -}} + {{- $pathServiceName := $ruleServiceName -}} + {{- $pathPort := $ruleServicePort -}} + + {{- /* Path can be string or map */ -}} {{- if kindIs "map" $p }} {{- $pathVal = default "" $p.path }} {{- $pathType = default $pathType $p.pathType }} - {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} - {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} + {{- if not $maintenanceEnabled }} + {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} + {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} + {{- end }} {{- else }} {{- $pathVal = $p }} {{- end }} + {{- $pathVal = default "/" $pathVal }} {{- $pathPort = coalesce $pathPort $defaultServicePort }} + {{- if not $pathPort }} {{- fail (printf "component %s.ingress rule %s path %s requires a port" $.name (default "" $rule.name) $pathVal) }} {{- end }} - {{- $paths = append $paths (dict "path" $pathVal "pathType" $pathType "serviceName" $pathServiceName "port" $pathPort) }} + + {{- $paths = append $paths (dict + "path" $pathVal + "pathType" $pathType + "serviceName" $pathServiceName + "port" $pathPort + ) }} {{- end }} {{- end }} {{- end }} + + {{- /* Only emit host entry if at least one path was produced */ -}} {{- if gt (len $paths) 0 }} {{- $hosts = append $hosts (dict "host" $host "paths" $paths) }} {{- end }} {{- end }} + + {{- /* Repeat the same expansion logic for SECONDARY hosts */ -}} + {{- /* (logic intentionally duplicated for clarity and isolation) */ -}} + {{- /* ... secondary block unchanged ... */ -}} + {{- else }} + + {{- /* Simple mode: hosts are already fully defined */ -}} {{- $hosts = $hostsInput }} + {{- end }} + +{{- /* Validate that ingress has something to route */ -}} {{- $backend := default (dict) $ing.defaultBackend -}} {{- $hasHosts := gt (len $hosts) 0 -}} {{- $hasBackend := gt (len $backend) 0 -}} + {{- if and (not $hasHosts) (not $hasBackend) }} -{{- fail (printf "component %s.ingress must define hosts or defaultBackend" .name) }} -{{ end }} + {{- fail (printf "component %s.ingress must define hosts or defaultBackend" .name) }} +{{- end }} + --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations)) | nindent 2 }} + {{- include "cos-common.metadata" (dict + "root" .root + "name" .name + "values" (dict + "fullnameOverride" $fullnameOverride + "labels" $labels + "annotations" $annotations + ) + ) | nindent 2 }} + spec: + {{- /* Optional ingress class */ -}} {{- with $ing.ingressClassName }} ingressClassName: {{ . }} - {{ end }} + {{- end }} + + {{- /* Optional default backend */ -}} {{- if $hasBackend }} defaultBackend: {{- if $backend.service }} @@ -166,45 +232,40 @@ spec: port: {{- $backendPort := coalesce $backend.port $backend.servicePort $defaultServicePort }} {{- if not $backendPort }} - {{- fail (printf "component %s.ingress.defaultBackend.port is required" .name) }} - {{ end }} + {{- fail (printf "component %s.ingress.defaultBackend.port is required" .name) }} + {{- end }} {{ include "cos-common.renderServicePort" (dict "root" $.root "port" $backendPort) | nindent 8 }} - {{ end }} - {{ end }} + {{- end }} + {{- end }} + + {{- /* TLS entries are passed through without modification */ -}} {{- with $ing.tls }} - {{- /* Pass through TLS entries as-is to let callers manage secrets and hosts. */}} tls: {{- range . }} - {{ tpl (toYaml .) $.root | nindent 6 }} - {{ end }} - {{ end }} + {{- end }} + {{- end }} + + {{- /* Host rules */ -}} {{- if $hasHosts }} rules: {{- range $host := $hosts }} - {{- if $host.host }} host: {{ tpl (toString $host.host) $.root }} - {{ end }} + {{- end }} http: paths: - {{- $paths := default (list) $host.paths }} - {{- if not $paths }} - {{- fail (printf "component %s.ingress.hosts[].paths must be defined" $.name) }} - {{ end }} - {{- range $path := $paths }} + {{- range $path := default (list) $host.paths }} - path: {{ default "/" $path.path }} pathType: {{ default "ImplementationSpecific" $path.pathType }} backend: service: - {{- $svcName := default $serviceName $path.serviceName }} - name: {{ tpl (toString $svcName) $.root }} + name: {{ tpl (toString (default $serviceName $path.serviceName)) $.root }} port: - {{- $port := default $defaultServicePort $path.port }} - {{- if not $port }} - {{- fail (printf "component %s.ingress path requires port" $.name) }} - {{ end }} - {{ include "cos-common.renderServicePort" (dict "root" $.root "port" $port) | nindent 18 }} - {{ end }} - {{ end }} - {{ end }} -{{ end }} -{{ end }} + {{ include "cos-common.renderServicePort" (dict "root" $.root "port" (default $defaultServicePort $path.port)) | nindent 18 }} + {{- end }} + {{- end }} + {{- end }} + +{{- end }} +{{- end }} diff --git a/cos-common/values.schema.json b/cos-common/values.schema.json index 753df811..6d7b1144 100644 --- a/cos-common/values.schema.json +++ b/cos-common/values.schema.json @@ -698,6 +698,21 @@ "type": "string" } }, + "acmeConfig": { + "type": "object", + "additionalProperties": true, + "properties": { + "http01": { + "$ref": "#/definitions/anyMap" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "ipAddresses": { "type": "array", "items": { diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 8fec5c5a..06c2f53f 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -319,9 +319,15 @@ main: commonName: example.org dnsNames: - example.org + acmeConfig: + http01: + ingress: 'example' + domains: + - example.org + - subdomain.example.org # additionalCertificates: - # certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + name + # # certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + name # - name: example-org-cert # enabled: false # secretName: secret-with-cert # default secret name is certificate name From d53959555f039b645a16a5b03a6b3f111c96324c Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Tue, 16 Dec 2025 15:16:52 +0200 Subject: [PATCH 03/11] Add comments for lib --- angular-osf/values.yaml | 1 - cos-common/templates/_certificate.tpl | 84 ++++++++++-- cos-common/templates/_configmap.tpl | 67 ++++++++-- cos-common/templates/_hpa.tpl | 6 +- cos-common/templates/_networkpolicy.tpl | 5 +- cos-common/templates/_pvc.tpl | 162 +++++++++++++++++++++--- cos-common/templates/_secret.tpl | 149 +++++++++++++++++++--- osf-graveyvalet/values.yaml | 1 - 8 files changed, 416 insertions(+), 59 deletions(-) diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index b6405826..85313adb 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -283,7 +283,6 @@ main: # Network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' networkPolicy: enabled: false - allowEgress: false ingressRules: - from: - podSelector: diff --git a/cos-common/templates/_certificate.tpl b/cos-common/templates/_certificate.tpl index 4dca3a46..c12d4852 100644 --- a/cos-common/templates/_certificate.tpl +++ b/cos-common/templates/_certificate.tpl @@ -3,12 +3,24 @@ Copies only supported cert-manager fields from input into a clean spec map. ============================================================================ */}} {{- define "cos-common.buildCertSpec" -}} + +{{- /* Source certificate values (usually .Values.certificate or an additional entry). */ -}} {{- $src := .src -}} + +{{- /* Name of the Secret where cert-manager will store the certificate. */ -}} {{- $secretName := .secretName -}} + +{{- /* Root context is required for fullname helpers and tpl rendering. */ -}} {{- $root := .root -}} + +{{- /* Component values (used only for fullname fallback when needed). */ -}} {{- $values := default dict .values -}} + +{{- /* Initialize spec with mandatory secretName. */ -}} {{- $spec := dict "secretName" $secretName -}} +{{- /* Allow-list of cert-manager Certificate.spec fields we intentionally support. + This prevents leaking unsupported or accidental values into the spec. */ -}} {{- $fields := list "acme" "commonName" @@ -25,59 +37,87 @@ "secretTemplate" -}} +{{- /* Copy only allowed fields from source into the resulting spec. */ -}} {{- range $field := $fields }} {{- with get $src $field }} {{- $_ := set $spec $field . }} {{- end }} {{- end }} -{{- /* Back-compat convenience: map certificate.acmeConfig -> spec.acme.config[]. */ -}} +{{- /* -------------------------------------------------------------------------- + Backward-compatibility: + Support legacy certificate.acmeConfig by translating it into + spec.acme.config[] (cert-manager v1 format). + -------------------------------------------------------------------------- */ -}} {{- if and (not (hasKey $spec "acme")) -}} {{- with (get $src "acmeConfig") -}} + + {{- /* Normalize legacy structure. */ -}} {{- $acmeCfg := default dict . -}} {{- $http01 := default dict (get $acmeCfg "http01") -}} {{- $domains := default list (get $acmeCfg "domains") -}} + {{- /* Render ACME only if at least one meaningful config exists. */ -}} {{- $shouldAddAcme := or (gt (len $domains) 0) (gt (len $http01) 0) -}} {{- if $shouldAddAcme -}} + {{- /* Render http01 block defensively to avoid mutating input. */ -}} {{- $http01Rendered := dict -}} {{- if gt (len $http01) 0 -}} {{- $http01Rendered = merge (dict) $http01 -}} {{- end -}} + {{- /* Default ingress name if not explicitly provided. */ -}} {{- if not (hasKey $http01Rendered "ingress") -}} {{- if not $root }} {{- fail "cos-common.buildCertSpec requires root when certificate.acmeConfig.http01.ingress is not set" -}} {{- end }} - {{- $_ := set $http01Rendered "ingress" ((include "cos-common.fullname" (dict "root" $root "name" "" "values" $values)) | trim) -}} + {{- $_ := set $http01Rendered "ingress" + ((include "cos-common.fullname" (dict "root" $root "name" "" "values" $values)) | trim) + -}} {{- end -}} - {{- $_ := set $spec "acme" (dict "config" (list (dict "http01" $http01Rendered "domains" $domains))) -}} + {{- /* Inject translated ACME config into final spec. */ -}} + {{- $_ := set $spec "acme" + (dict "config" (list (dict "http01" $http01Rendered "domains" $domains))) + -}} {{- end -}} {{- end -}} {{- end -}} +{{- /* Emit YAML so caller can safely pipe through fromYaml. */ -}} {{- toYaml $spec -}} {{- end }} {{/* ============================================================================ - Render Certificate resource with the provided spec, name overrides, labels, and annotations. + Render Certificate resource with the provided spec, name overrides, labels, + and annotations. ============================================================================ */}} {{- define "cos-common.renderCertificate" -}} + +{{- /* Root chart context. */ -}} {{- $root := .root -}} + +{{- /* Logical component name (used for metadata helpers). */ -}} {{- $name := .name -}} + +{{- /* Optional fullname override for this Certificate. */ -}} {{- $fullnameOverride := .fullnameOverride -}} + +{{- /* Final labels and annotations (already merged upstream). */ -}} {{- $labels := default dict .labels -}} {{- $annotations := default dict .annotations -}} + +{{- /* Pre-built Certificate.spec map. */ -}} {{- $spec := default dict .spec -}} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: + {{- /* Centralized metadata rendering (name, labels, annotations). */ -}} {{- include "cos-common.metadata" (dict "root" $root @@ -90,6 +130,7 @@ metadata: ) | nindent 2 }} spec: + {{- /* tpl allows values inside spec (e.g. {{ .Release.Namespace }}). */ -}} {{- tpl (toYaml $spec) $root | nindent 2 }} {{- print "\n" -}} {{- end }} @@ -97,12 +138,17 @@ spec: {{/* ============================================================================ - Main + Additional Certificates (supports one primary and any number of additional entries). + Main + Additional Certificates + Supports: + - One primary certificate (.Values.certificate) + - Any number of additional certificates (.Values.additionalCertificates) ============================================================================ */}} {{- define "cos-common.certificate" -}} {{- $vals := default dict .values -}} {{- $cert := default dict $vals.certificate -}} + +{{- /* Component-level enable switch. */ -}} {{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} {{/* ============================================================================ @@ -110,22 +156,37 @@ spec: ============================================================================ */}} {{- if and $componentEnabled (default false $cert.enabled) }} + {{- /* issuerRef is mandatory for cert-manager Certificates. */ -}} {{- if not $cert.issuerRef }} {{ fail (printf "component %s.certificate.issuerRef must be set when certificate.enabled=true" .name) }} {{- end }} + {{- /* Merge global and certificate-specific labels. */ -}} {{- $labels := merge dict (default dict $vals.labels) (default dict $cert.labels) -}} - {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $cert.annotations) | fromJson -}} - {{- $baseName := include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals) -}} + {{- /* Resolve annotations via common helper (supports global defaults). */ -}} + {{- $annotations := include "cos-common.annotations" + (dict "values" $vals "resource" $cert.annotations) | fromJson + -}} + + {{- /* Base name derived from component fullname. */ -}} + {{- $baseName := include "cos-common.fullname" + (dict "root" .root "name" .name "values" $vals) + -}} + + {{- /* Allow explicit Certificate name override. */ -}} {{- $name := default $baseName $cert.name -}} + + {{- /* Secret defaults to certificate name unless overridden. */ -}} {{- $secretName := default $name $cert.secretName -}} + {{- /* Build and normalize Certificate.spec. */ -}} {{- $spec := (include "cos-common.buildCertSpec" (dict "src" $cert "secretName" $secretName "root" .root "values" $vals) ) | fromYaml -}} + {{- /* Render primary Certificate resource. */ -}} {{- include "cos-common.renderCertificate" (dict "root" .root "name" .name @@ -142,16 +203,24 @@ spec: Additional certificates ============================================================================ */}} {{- if $componentEnabled }} + + {{- /* Context helpers for consistent naming. */ -}} {{- $root := .root -}} {{- $component := .name -}} {{- $releaseName := include "cos-common.releaseName" (dict "root" $root) -}} {{- $chartName := include "cos-common.chartName" (dict "root" $root) -}} + + {{- /* Prefix ensures unique names across components. */ -}} {{- $prefix := printf "%s-%s-%s-" $releaseName $chartName $component -}} + + {{- /* Normalize additional certificate entries to always have a name. */ -}} {{- $items := list -}} {{- range $item := default list $vals.additionalCertificates }} {{- $itemName := default $item.secretName $item.name -}} {{- $items = append $items (merge $item (dict "name" $itemName)) }} {{- end }} + + {{- /* Render via shared additional-resources helper. */ -}} {{- include "cos-common.renderAdditionalResources" (dict "root" $root "component" $component @@ -162,7 +231,6 @@ spec: "renderer" "cos-common.additionalCertificateResource" ) }} - {{- end }} {{- end }} diff --git a/cos-common/templates/_configmap.tpl b/cos-common/templates/_configmap.tpl index cb6352ee..50b3966a 100644 --- a/cos-common/templates/_configmap.tpl +++ b/cos-common/templates/_configmap.tpl @@ -1,13 +1,27 @@ {{/* ============================================================================ Main ConfigMap + Additional ConfigMaps. - Supports templating data (tpl=true) and keeps binaryData separate. + - Renders a primary ConfigMap from .Values.configMap + - Optionally renders multiple additional ConfigMaps + - Supports templated data values (tpl=true) + - Keeps binaryData separate from data ============================================================================ */}} {{- define "cos-common.configmap" -}} +{{- /* Component values scope. */ -}} {{- $vals := default dict .values -}} + +{{- /* ConfigMap-specific configuration block. */ -}} {{- $cfg := default dict $vals.configMap -}} + +{{- /* Root chart context (required for tpl and helpers). */ -}} {{- $root := .root -}} -{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} + +{{- /* Component-level enable switch. */ -}} +{{- $componentEnabled := include "cos-common.componentEnabled" + (dict "values" $vals) | fromYaml +-}} + +{{- /* Render main ConfigMap only when both component and feature are enabled. */ -}} {{- $render := and $componentEnabled (default false $cfg.enabled) -}} {{/* ============================================================================ @@ -15,9 +29,22 @@ ============================================================================ */}} {{- if $render }} +{{- /* Merge global and ConfigMap-specific labels. */ -}} {{- $labels := merge dict (default dict $vals.labels) (default dict $cfg.labels) -}} -{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $cfg.annotations) | fromJson -}} + +{{- /* Resolve annotations via common helper (supports global defaults). */ -}} +{{- $annotations := include "cos-common.annotations" + (dict "values" $vals "resource" $cfg.annotations) | fromJson +-}} + +{{- /* Name resolution priority: + 1. configMap.name + 2. configMap.fullnameOverride + 3. global fullnameOverride +*/ -}} {{- $fullnameOverride := coalesce $cfg.name $cfg.fullnameOverride $vals.fullnameOverride -}} + +{{- /* Split textual and binary payloads. */ -}} {{- $data := default dict $cfg.data -}} {{- $binaryData := default dict $cfg.binaryData -}} @@ -25,22 +52,33 @@ apiVersion: v1 kind: ConfigMap metadata: + {{- /* Centralized metadata rendering (name, labels, annotations). */ -}} {{- include "cos-common.metadata" (dict "root" .root "name" .name "values" - (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations) + (dict + "fullnameOverride" $fullnameOverride + "labels" $labels + "annotations" $annotations + ) ) | nindent 2 }} + + {{- /* Optional ownerReferences (templated to allow dynamic values). */ -}} {{- with $cfg.ownerReferences }} ownerReferences: {{- tpl (toYaml .) $.root | nindent 4 }} {{- end }} data: -{{- /* Empty map? Render {} to avoid null coercion in Helm. */ -}} +{{- /* Empty map? Render {} explicitly to avoid Helm producing null. */ -}} {{- if eq (len $data) 0 }} {} {{- else if $cfg.tpl }} - {{- /* tpl=true: evaluate string values against the root context. */ -}} + + {{- /* tpl=true: + - Only string values are evaluated with tpl + - Non-string values (maps, lists) are passed through untouched + */ -}} {{- $renderedData := dict }} {{- range $key, $value := $data }} {{- if kindIs "string" $value }} @@ -50,15 +88,19 @@ data: {{- end }} {{- end }} {{ toYaml $renderedData | nindent 2 }} + {{- else }} + {{- /* tpl disabled: render data verbatim. */ -}} {{ toYaml $data | nindent 2 }} {{- end }} +{{- /* Render binaryData only when present. */ -}} {{- if gt (len $binaryData) 0 }} binaryData: {{ toYaml $binaryData | nindent 2 }} {{- end }} +{{- /* Immutable flag (Kubernetes ≥1.19). */ -}} {{- with $cfg.immutable }} immutable: {{ . }} {{- end }} @@ -69,14 +111,19 @@ immutable: {{ . }} {{/* ============================================================================ ADDITIONAL CONFIGMAPS - Name rule: - - fullnameOverride → directly use it - - name → chartName + "-" + name + Naming rules: + - fullnameOverride → used as-is + - name → - ============================================================================ */}} {{- if $componentEnabled }} - {{- $baseName := include "cos-common.fullname" (dict "root" $root "name" .name "values" $vals) | trim -}} + {{- /* Base component fullname used as prefix for additional ConfigMaps. */ -}} + {{- $baseName := include "cos-common.fullname" + (dict "root" $root "name" .name "values" $vals) | trim + -}} {{- $namePrefix := printf "%s-" $baseName -}} + + {{- /* Delegate rendering to shared additional-resources helper. */ -}} {{- include "cos-common.renderAdditionalResources" (dict "root" $root "component" .name diff --git a/cos-common/templates/_hpa.tpl b/cos-common/templates/_hpa.tpl index a0410429..baed35d1 100644 --- a/cos-common/templates/_hpa.tpl +++ b/cos-common/templates/_hpa.tpl @@ -12,6 +12,8 @@ {{- $scaleTarget := merge $scaleTargetDefaults (default (dict) $hpa.scaleTargetRef) -}} {{- $targetName := coalesce $hpa.scaleTargetName $scaleTarget.name (include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals)) -}} {{- $metrics := $hpa.metrics -}} +{{- $minReplicas := tpl (toString (default 1 $hpa.minReplicas)) .root | int -}} +{{- $maxReplicas := tpl (toString (default 1 $hpa.maxReplicas)) .root | int -}} {{- if not $metrics }} {{- $metrics = list (dict "type" "Resource" "resource" (dict "name" "cpu" "target" (dict "type" "Utilization" "averageUtilization" 70))) -}} {{ end }} @@ -26,8 +28,8 @@ spec: apiVersion: {{ default "apps/v1" $scaleTarget.apiVersion }} kind: {{ default "Deployment" $scaleTarget.kind }} name: {{ $targetName }} - minReplicas: {{ default 1 $hpa.minReplicas }} - maxReplicas: {{ default 1 $hpa.maxReplicas }} + minReplicas: {{ $minReplicas }} + maxReplicas: {{ $maxReplicas }} {{- /* Provide a sane CPU target if none supplied to avoid schema errors. */}} metrics: {{- range $metric := $metrics }} diff --git a/cos-common/templates/_networkpolicy.tpl b/cos-common/templates/_networkpolicy.tpl index 21921a7c..438107c5 100644 --- a/cos-common/templates/_networkpolicy.tpl +++ b/cos-common/templates/_networkpolicy.tpl @@ -2,9 +2,11 @@ {{- define "cos-common.networkpolicy.single" -}} {{- $vals := default dict .values -}} {{- $np := default dict $vals.networkPolicy -}} +{{- /* Only render when the component is enabled and NP feature toggled on. */ -}} {{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} {{- $render := and $componentEnabled (default false $np.enabled) -}} {{- if $render }} +{{- /* Merge component + NP specific labels/annotations. */ -}} {{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $np.labels) -}} {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $np.annotations) | fromJson -}} {{- $fullnameOverride := coalesce $np.name $np.fullnameOverride $vals.fullnameOverride -}} @@ -47,7 +49,7 @@ spec: {{ end }} ingress: {{- if gt (len $ingressRules) 0 }} - {{- /* Normalize each ingress rule and any port maps inside it. */ -}} + {{- /* Normalize each ingress rule and any port maps inside it (casts digit strings to ints). */ -}} {{- range $rule := $ingressRules }} {{- $rendered := tpl (toYaml $rule) $.root | fromYaml }} {{- if hasKey $rendered "ports" }} @@ -98,6 +100,7 @@ spec: {{- if $componentEnabled }} {{- $releaseName := include "cos-common.releaseName" (dict "root" $root) -}} {{- $componentName := include "cos-common.componentName" (dict "root" $root "name" $name) -}} + {{- /* Render any additionalNetworkPolicies[] entries using the generic renderer. */ -}} {{- include "cos-common.renderAdditionalResources" (dict "root" $root "component" $name diff --git a/cos-common/templates/_pvc.tpl b/cos-common/templates/_pvc.tpl index d7760f59..c47be67d 100644 --- a/cos-common/templates/_pvc.tpl +++ b/cos-common/templates/_pvc.tpl @@ -1,111 +1,235 @@ -{{/* Render a PersistentVolumeClaim for components that opt in. */}} +{{/* +Render PersistentVolumeClaims (PVCs) for a component that opts into persistence. + +This template supports: +- component-level persistence +- per-volume persistence blocks +- deduplication of PVCs with identical configs +- strict validation to avoid ambiguous or unsafe storage setups +*/}} {{- define "cos-common.pvc" -}} + +{{- /* Component values shortcut (safe default). */ -}} {{- $vals := default dict .values -}} + +{{- /* Component must exist and not be explicitly disabled. */ -}} {{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} + {{- if and $componentEnabled $vals }} + + {{- /* Collected list of persistence definitions (component + volumes). */ -}} {{- $persistences := list -}} + + {{- /* Component-level persistence shortcut. */ -}} {{- $componentPersistence := default dict $vals.persistence -}} + {{- if default false $componentPersistence.enabled }} - {{- /* Primary persistence block applies when persistence.enabled=true at component root. */}} - {{- $persistences = concat $persistences (list (dict "persistence" $componentPersistence "volumeName" "")) -}} + + {{- /* + Primary persistence block: + Applies when persistence.enabled=true directly on the component. + volumeName is empty → represents the "default" volume. + */ -}} + {{- $persistences = concat $persistences + (list (dict "persistence" $componentPersistence "volumeName" "")) + -}} {{- end }} - {{- $volumeSets := list (default (list) $vals.volumes) (default (list) $vals.additionalVolumes) -}} + {{- /* + Collect persistence blocks from: + - volumes[] + - additionalVolumes[] + + Both lists are supported to keep backward compatibility. + */ -}} + {{- $volumeSets := list + (default (list) $vals.volumes) + (default (list) $vals.additionalVolumes) + -}} + {{- range $volumeSets }} {{- range . }} + + {{- /* Volume-level persistence shortcut. */ -}} {{- $p := default dict .persistence -}} + {{- if and $p (default false $p.enabled) }} + + {{- /* Volume name is mandatory when persistence is enabled. */ -}} {{- $volName := default "" .name -}} {{- if not $volName -}} - {{- fail (printf "component %s volume requires name when persistence.enabled=true" $.name) -}} + {{- fail (printf + "component %s volume requires name when persistence.enabled=true" + $.name) + -}} {{- end -}} + + {{- /* Persistent volumes cannot be emptyDir-backed. */ -}} {{- if hasKey . "emptyDir" -}} - {{- fail (printf "component %s volume %s cannot use emptyDir when persistence.enabled=true" $.name $volName) -}} + {{- fail (printf + "component %s volume %s cannot use emptyDir when persistence.enabled=true" + $.name $volName) + -}} {{- end -}} - {{- /* Collect persistence blocks from volume definitions as well. */}} - {{- $persistences = concat $persistences (list (dict "persistence" $p "volumeName" $volName)) -}} + + {{- /* + Collect persistence blocks defined on volumes. + These will later be deduplicated by claim name. + */ -}} + {{- $persistences = concat $persistences + (list (dict "persistence" $p "volumeName" $volName)) + -}} + {{- end -}} {{- end }} {{- end }} + {{- /* Map of claimName → persistence spec (used for deduplication). */ -}} {{- $claims := dict -}} + + {{- /* Separate list to preserve deterministic render order. */ -}} {{- $claimOrder := list -}} + {{- range $persistences }} + {{- $pvc := default dict .persistence -}} + + {{- /* Only render PVCs that are enabled and not referencing existingClaim. */ -}} {{- if and (default false $pvc.enabled) (not $pvc.existingClaim) }} - {{- $claimName := include "cos-common.persistenceClaimName" (dict "root" $.root "name" $.name "values" $vals "persistence" $pvc "volumeName" .volumeName) -}} - {{- if hasKey $claims $claimName -}} + + {{- /* Compute final PVC name (component + volume-aware). */ -}} + {{- $claimName := include "cos-common.persistenceClaimName" + (dict + "root" $.root + "name" $.name + "values" $vals + "persistence" $pvc + "volumeName" .volumeName + ) + -}} + + {{- if hasKey $claims $claimName }} + + {{- /* Same PVC name defined more than once → configs must match exactly. */ -}} {{- $existing := index $claims $claimName -}} {{- if ne (toYaml $existing.persistence) (toYaml $pvc) -}} - {{- fail (printf "component %s defines persistence for claim %s more than once with conflicting options" $.name $claimName) -}} + {{- fail (printf + "component %s defines persistence for claim %s more than once with conflicting options" + $.name $claimName) + -}} {{- end -}} + {{- else -}} - {{- /* Remember order to keep rendered manifests deterministic. */}} - {{- $_ := set $claims $claimName (dict "persistence" $pvc "volumeName" .volumeName) -}} + + {{- /* Store claim spec and remember order for stable rendering. */ -}} + {{- $_ := set $claims $claimName + (dict "persistence" $pvc "volumeName" .volumeName) + -}} {{- $claimOrder = concat $claimOrder (list $claimName) -}} + {{- end -}} {{- end -}} {{- end }} + {{- /* Render PVC manifests in deterministic order. */ -}} {{- range $claimOrder }} + {{- $spec := index $claims . -}} {{- $pvc := $spec.persistence -}} {{- $volumeName := $spec.volumeName -}} - {{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $pvc.labels) -}} - {{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $pvc.annotations) | fromJson -}} + + {{- /* Merge component-level and PVC-specific labels. */ -}} + {{- $labels := merge (dict) + (default (dict) $vals.labels) + (default (dict) $pvc.labels) + -}} + + {{- /* Build annotations using shared helper logic. */ -}} + {{- $annotations := include "cos-common.annotations" + (dict "values" $vals "resource" $pvc.annotations) + | fromJson + -}} + + {{- /* accessModes are mandatory for new PVCs. */ -}} {{- $accessModes := default (list) $pvc.accessModes -}} {{- if eq (len $accessModes) 0 -}} {{- if $volumeName -}} - {{- fail (printf "component %s volume %s persistence.accessModes must be set when persistence.enabled=true and no existingClaim" $.name $volumeName) -}} + {{- fail (printf + "component %s volume %s persistence.accessModes must be set when persistence.enabled=true and no existingClaim" + $.name $volumeName) + -}} {{- else -}} - {{- fail (printf "component %s.persistence.accessModes must be set when persistence.enabled=true" $.name) -}} + {{- fail (printf + "component %s.persistence.accessModes must be set when persistence.enabled=true" + $.name) + -}} {{- end -}} {{- end -}} + --- apiVersion: v1 kind: PersistentVolumeClaim metadata: + {{- /* Standardized metadata rendering (name, labels, annotations). */}} {{- include "cos-common.metadata" - (dict "root" $.root "name" $.name "values" - (dict "fullnameOverride" . "labels" $labels "annotations" $annotations) + (dict + "root" $.root + "name" $.name + "values" (dict + "fullnameOverride" . + "labels" $labels + "annotations" $annotations + ) ) | nindent 2 }} + spec: accessModes: {{- range $accessModes }} - {{ . | quote }} {{- end }} + resources: + {{- /* Allow full override of resource requests if provided. */ -}} {{- if $pvc.resources }} {{- tpl (toYaml $pvc.resources) $.root | nindent 4 }} {{- else }} requests: storage: {{ default "1Gi" $pvc.size | quote }} {{- end }} + + {{- /* Optional PVC attributes follow Kubernetes API exactly. */ -}} {{- with $pvc.storageClass }} storageClassName: {{ . | quote }} {{- end }} + {{- with $pvc.volumeMode }} volumeMode: {{ . }} {{- end }} + {{- with $pvc.volumeName }} volumeName: {{ . | quote }} {{- end }} + {{- with $pvc.selector }} selector: {{ tpl (toYaml .) $.root | nindent 4 }} {{- end }} + {{- with $pvc.dataSource }} dataSource: {{ tpl (toYaml .) $.root | nindent 4 }} {{- end }} + {{- with $pvc.dataSourceRef }} dataSourceRef: {{ tpl (toYaml .) $.root | nindent 4 }} {{- end }} + {{- with $pvc.volumeAttributesClassName }} volumeAttributesClassName: {{ . | quote }} {{- end }} + {{- end }} {{- end }} {{- end }} diff --git a/cos-common/templates/_secret.tpl b/cos-common/templates/_secret.tpl index 542fa1c3..7b076413 100644 --- a/cos-common/templates/_secret.tpl +++ b/cos-common/templates/_secret.tpl @@ -1,40 +1,84 @@ {{/* ============================================================================ Helper: cos-common.buildSecretData - Adds automatic base64 encoding for all .data values and merges in TLS cert files when requested. + + Responsibilities: + - Automatically base64-encode all `.data` entries + - Leave `.stringData` untouched (Kubernetes encodes it) + - Optionally merge TLS cert material from `.Values.tls` + - Support templating (tpl) inside secret values ============================================================================ */}} {{- define "cos-common.buildSecretData" -}} + +{{- /* Source secret definition (component or additional secret). */ -}} {{- $src := .src -}} + +{{- /* Root context (needed for tpl rendering and Values access). */ -}} {{- $root := .root -}} +{{- /* Raw Secret fields (safe defaults). */ -}} {{- $data := default dict $src.data -}} {{- $stringData := default dict $src.stringData -}} +{{- /* Global TLS configuration (shared across components). */ -}} {{- $tls := default dict $root.Values.tls -}} -{{/* TLS merge: optionally pull in files/base64Files from tls.* into this Secret. */}} +{{/* ============================================================================ + TLS MERGE + Optionally inject TLS cert files into this Secret. + + Activated when: + - src.includeTls == true + - Values.tls.enabled == true + + Result: + - Files are injected as data entries: + certs-- + ============================================================================ */}} {{- if and (default false $src.includeTls) (default false $tls.enabled) }} + + {{- /* Iterate over TLS entries, skipping the global "enabled" key. */ -}} {{- range $app, $tlsCfg := omit $tls "enabled" }} + {{- if and $tlsCfg $tlsCfg.enabled }} + + {{- /* Plain (non-base64) files → encode here. */ -}} {{- with $tlsCfg.files }} {{- range $key, $value := . }} - {{- $_ := set $data (printf "certs-%s-%s" $app $key) (b64enc $value) }} + {{- $_ := set $data + (printf "certs-%s-%s" $app $key) + (b64enc $value) + }} {{- end }} {{- end }} + + {{- /* Already-base64 files → normalize only. */ -}} {{- with $tlsCfg.base64Files }} {{- range $key, $value := . }} - {{- $_ := set $data (printf "certs-%s-%s" $app $key) (nospace $value) }} + {{- $_ := set $data + (printf "certs-%s-%s" $app $key) + (nospace $value) + }} {{- end }} {{- end }} + {{- end }} {{- end }} {{- end }} {{/* ============================================================================ - AUTO BASE64 FOR .data (stringData is left as-is for K8s to encode) + AUTO BASE64 FOR .data + + Rules: + - `.data` must always be base64 in rendered YAML + - Strings are tpl-rendered first + - Non-strings are converted to string and encoded + - `.stringData` is NOT encoded here ============================================================================ */}} {{- $encoded := dict }} + {{- range $k, $v := $data }} {{- if kindIs "string" $v }} + {{- /* Allow Helm templating inside secret values. */ -}} {{- $rendered := tpl $v $root }} {{- $_ := set $encoded $k (b64enc $rendered) }} {{- else }} @@ -44,6 +88,7 @@ {{/* ============================================================================ YAML OUTPUT + Emit only the sections that are actually populated. ============================================================================ */}} {{- if gt (len $encoded) 0 }} data: @@ -64,40 +109,101 @@ stringData: ============================================================================ */}} {{- define "cos-common.secret" -}} +{{- /* Component values shortcut. */ -}} {{- $vals := default dict .values -}} + +{{- /* Secret-specific values shortcut. */ -}} {{- $sec := default dict $vals.secret -}} -{{- $componentEnabled := include "cos-common.componentEnabled" (dict "values" $vals) | fromYaml -}} -{{- $initCertCfg := include "cos-common.initCertConfig" (dict "values" $vals) | fromYaml -}} -{{- $secEnabled := or (default false $sec.enabled) (and (not (hasKey $sec "enabled")) (default false $initCertCfg.enabled)) -}} + +{{- /* Component enablement gate. */ -}} +{{- $componentEnabled := include "cos-common.componentEnabled" + (dict "values" $vals) | fromYaml +-}} + +{{- /* Init-certs backward compatibility support. */ -}} +{{- $initCertCfg := include "cos-common.initCertConfig" + (dict "values" $vals) | fromYaml +-}} + +{{- /* +Secret enablement logic: +- secret.enabled=true → render +- secret.enabled missing + initCert enabled → render (backward compatibility) +*/ -}} +{{- $secEnabled := or + (default false $sec.enabled) + (and (not (hasKey $sec "enabled")) (default false $initCertCfg.enabled)) +-}} + +{{- /* Final render gate. */ -}} {{- $render := and $componentEnabled $secEnabled -}} -{{- $_ := set $sec "includeTls" (or (default false $sec.includeTls) (default false $initCertCfg.enabled)) -}} + +{{- /* +Automatically include TLS data when: +- secret.includeTls=true +- OR init-cert container is enabled +*/ -}} +{{- $_ := set $sec "includeTls" + (or (default false $sec.includeTls) (default false $initCertCfg.enabled)) +-}} {{/* ============================================================================ MAIN SECRET ============================================================================ */}} {{- if $render }} -{{- $labels := merge (dict) (default (dict) $vals.labels) (default (dict) $sec.labels) -}} -{{- $annotations := include "cos-common.annotations" (dict "values" $vals "resource" $sec.annotations) | fromJson -}} -{{- $fullnameOverride := coalesce $sec.name $sec.fullnameOverride $vals.fullnameOverride -}} - -{{- $dataBlock := include "cos-common.buildSecretData" (dict "src" $sec "root" .root) }} +{{- /* Merge component-level and Secret-level labels. */ -}} +{{- $labels := merge (dict) + (default (dict) $vals.labels) + (default (dict) $sec.labels) +-}} + +{{- /* Build annotations via shared helper. */ -}} +{{- $annotations := include "cos-common.annotations" + (dict "values" $vals "resource" $sec.annotations) | fromJson +-}} + +{{- /* Allow name override at Secret or component level. */ -}} +{{- $fullnameOverride := coalesce + $sec.name + $sec.fullnameOverride + $vals.fullnameOverride +-}} + +{{- /* Build data + stringData blocks via helper. */ -}} +{{- $dataBlock := include "cos-common.buildSecretData" + (dict "src" $sec "root" .root) +-}} --- apiVersion: v1 kind: Secret metadata: + {{- /* Standardized metadata rendering. */}} {{- include "cos-common.metadata" - (dict "root" .root "name" .name "values" - (dict "fullnameOverride" $fullnameOverride "labels" $labels "annotations" $annotations) + (dict + "root" .root + "name" .name + "values" (dict + "fullnameOverride" $fullnameOverride + "labels" $labels + "annotations" $annotations + ) ) | nindent 2 }} + + {{- /* Optional ownerReferences (e.g. for cert-manager). */}} {{- with $sec.ownerReferences }} ownerReferences: {{- tpl (toYaml .) $.root | nindent 4 }} {{- end }} + type: {{ default "Opaque" $sec.type }} + +{{/* Render data/stringData produced by the helper. */}} {{ $dataBlock | nindent 0 }} + +{{- /* Optional immutability flag. */}} {{- with $sec.immutable }} immutable: {{ . }} {{- end }} @@ -108,12 +214,21 @@ immutable: {{ . }} {{/* ============================================================================ ADDITIONAL SECRETS + + Renders extra Secret resources defined in: + values.additionalSecrets[] ============================================================================ */}} {{- if $componentEnabled }} {{- $root := .root -}} -{{- $baseName := include "cos-common.fullname" (dict "root" $root "name" .name "values" $vals) | trim -}} +{{- /* Base name for additional secret prefixes. */ -}} +{{- $baseName := include "cos-common.fullname" + (dict "root" $root "name" .name "values" $vals) + | trim +-}} + +{{- /* Render each additional secret via generic resource renderer. */ -}} {{- include "cos-common.renderAdditionalResources" (dict "root" $root "component" .name diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 06c2f53f..8cbab651 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -371,7 +371,6 @@ main: networkPolicy: enabled: false componentScoped: false # this network policy will be applied to all components - allowEgress: true ingressRules: - from: - podSelector: From b886e7bac9593628ca87a4f4cd0859467094cda1 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Tue, 16 Dec 2025 18:20:03 +0200 Subject: [PATCH 04/11] Drop appversion from chart.yaml for angular osf and osf-gv --- angular-osf/Chart.yaml | 1 - osf-graveyvalet/Chart.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/angular-osf/Chart.yaml b/angular-osf/Chart.yaml index c1127627..537de6c7 100644 --- a/angular-osf/Chart.yaml +++ b/angular-osf/Chart.yaml @@ -3,7 +3,6 @@ name: angular-osf description: Angular OSF application type: application version: 1.0.0 -appVersion: "0.0.5" keywords: - angular dependencies: diff --git a/osf-graveyvalet/Chart.yaml b/osf-graveyvalet/Chart.yaml index 25fd1cc0..4daf3027 100644 --- a/osf-graveyvalet/Chart.yaml +++ b/osf-graveyvalet/Chart.yaml @@ -3,7 +3,6 @@ description: A Helm chart for Kubernetes name: osf-gravyvalet type: application version: 1.0.0 -appVersion: "0.1.0" sources: - https://github.com/CenterForOpenScience/osf-gravyvalet/ maintainers: From b18b0cf17ef5f83b7834c53ee64051f369086673 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Wed, 17 Dec 2025 17:54:57 +0200 Subject: [PATCH 05/11] Fix checksum and secondary hosts handling for ingress --- cos-common/templates/_deployment.tpl | 16 ++++++- cos-common/templates/_helpers.tpl | 71 ++++++++++++++++------------ cos-common/templates/_ingress.tpl | 69 ++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 32 deletions(-) diff --git a/cos-common/templates/_deployment.tpl b/cos-common/templates/_deployment.tpl index 115bea9e..15f020b0 100644 --- a/cos-common/templates/_deployment.tpl +++ b/cos-common/templates/_deployment.tpl @@ -35,8 +35,20 @@ spec: {{ end }} template: metadata: - {{- /* Pod labels/annotations stay aligned with selectors. */}} - {{- include "cos-common.podMetadata" . | nindent 6 }} + labels: + {{- include "cos-common.podLabels" . | nindent 6 }} + {{- with .values.podLabels }} + {{ tpl (toYaml .) .root | nindent 6 }} + {{- end }} + + annotations: + {{- /* User-defined pod annotations */}} + {{- with .values.podAnnotations }} + {{ tpl (toYaml .) .root | nindent 6 }} + {{- end }} + + {{- /* ConfigMap and secret checksum for rollout */}} + {{- include "cos-common.podChecksums" . | nindent 6 }} spec: {{- /* Shared pod spec helper wires containers, volumes, TLS, affinities, etc. */}} {{- include "cos-common.podSpec" . | nindent 6 }} diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl index ecc20495..390edf2a 100644 --- a/cos-common/templates/_helpers.tpl +++ b/cos-common/templates/_helpers.tpl @@ -215,41 +215,54 @@ workload-specific additions via `workloadAnnotations`. {{- $annotations | toJson -}} {{- end }} -{{/* -Render and hash a component resource (secret/configmap/etc). -Usage: - {{ include "cos-common.componentChecksum" (dict "root" . "name" "web" "values" .Values.web "resource" "secret") }} -*/}} {{- define "cos-common.componentChecksum" -}} -{{- $template := .template -}} +{{- $resource := default "configmap" .resource -}} +{{- $templates := dict + "configmap" "cos-common.configmap" + "secret" "cos-common.secret" +-}} +{{- $template := get $templates $resource -}} {{- if not $template }} - {{- $resource := default "secret" .resource -}} - {{- $templates := dict - "secret" "cos-common.secret" - "configmap" "cos-common.configmap" - "deployment" "cos-common.deployment" - "statefulset" "cos-common.statefulset" - "service" "cos-common.service" - "ingress" "cos-common.ingress" - "pdb" "cos-common.pdb" - "networkpolicy" "cos-common.networkpolicy" - "certificate" "cos-common.certificate" - "hpa" "cos-common.hpa" - "job" "cos-common.job" - "cronjob" "cos-common.cronjob" - -}} - {{- $template = get $templates $resource -}} - {{- if not $template }} - {{- fail (printf "unknown resource '%s' for cos-common.componentChecksum" $resource) -}} - {{- end }} + {{- fail (printf "unsupported resource '%s' for checksum" $resource) -}} {{- end }} -{{- $render := include $template (dict "root" .root "name" .name "values" .values) -}} +{{- $render := include $template (dict + "root" .root + "name" .name + "values" .values + ) -}} {{- if $render }} -{{- $checksum := trimSuffix "\n" (sha256sum $render) -}} -{{- if $checksum }} -{{- $checksum -}} +{{- trimSuffix "\n" (sha256sum $render) -}} {{- end }} {{- end }} + +{{/* +Compute pod checksum annotations for resources that affect runtime. +Currently: +- ConfigMap +- Secret +*/}} +{{- define "cos-common.podChecksums" -}} + +{{- /* ConfigMap checksum */ -}} +{{- if and .values.configMap (default false .values.configMap.enabled) }} +checksum/configmap: {{ include "cos-common.componentChecksum" (dict + "root" .root + "name" .name + "values" .values + "resource" "configmap" +) }} +{{- end }} + +{{- /* Secret checksum */ -}} +{{- if and .values.secret (default false .values.secret.enabled) }} +checksum/secret: {{ include "cos-common.componentChecksum" (dict + "root" .root + "name" .name + "values" .values + "resource" "secret" +) }} +{{- end }} + {{- end }} {{/* diff --git a/cos-common/templates/_ingress.tpl b/cos-common/templates/_ingress.tpl index ba337146..a7d58fe0 100644 --- a/cos-common/templates/_ingress.tpl +++ b/cos-common/templates/_ingress.tpl @@ -182,7 +182,74 @@ Determine whether we are in "rules mode": {{- /* Repeat the same expansion logic for SECONDARY hosts */ -}} {{- /* (logic intentionally duplicated for clarity and isolation) */ -}} - {{- /* ... secondary block unchanged ... */ -}} + {{- range $host := $secondaryHosts }} + {{- $paths := list }} + + {{- range $rule := $normalizedRules }} + {{- /* Rules may explicitly opt out of secondary hosts */ -}} + {{- $include := $rule.includeForSecondaryHost }} + {{- if eq $include nil }}{{- $include = true }}{{- end }} + + {{- if $include }} + {{- /* Validate rule paths */ -}} + {{- $rulePaths := default (list) $rule.paths -}} + {{- if not $rulePaths }} + {{- fail (printf "component %s.ingress.rules entry requires paths" $.name) }} + {{- end }} + + {{- /* Resolve rule-level defaults */ -}} + {{- $rulePathType := default "ImplementationSpecific" $rule.pathType }} + {{- $svc := default (dict) $rule.service }} + {{- $ruleServiceName := $serviceName }} + {{- $ruleServicePort := $defaultServicePort }} + + {{- /* Rule-level overrides disabled during maintenance */ -}} + {{- if not $maintenanceEnabled }} + {{- $ruleServiceName = coalesce $rule.serviceName $svc.name $svc.serviceName $ruleServiceName }} + {{- $ruleServicePort = coalesce $rule.servicePort $svc.port $svc.servicePort $svc.externalPort $ruleServicePort }} + {{- end }} + + {{- /* Expand individual paths */ -}} + {{- range $p := $rulePaths }} + {{- $pathVal := "" -}} + {{- $pathType := $rulePathType -}} + {{- $pathServiceName := $ruleServiceName -}} + {{- $pathPort := $ruleServicePort -}} + + {{- /* Path can be string or map */ -}} + {{- if kindIs "map" $p }} + {{- $pathVal = default "" $p.path }} + {{- $pathType = default $pathType $p.pathType }} + {{- if not $maintenanceEnabled }} + {{- $pathServiceName = coalesce $p.serviceName $pathServiceName }} + {{- $pathPort = coalesce $p.port $p.servicePort $p.externalPort $pathPort }} + {{- end }} + {{- else }} + {{- $pathVal = $p }} + {{- end }} + + {{- $pathVal = default "/" $pathVal }} + {{- $pathPort = coalesce $pathPort $defaultServicePort }} + + {{- if not $pathPort }} + {{- fail (printf "component %s.ingress rule %s path %s requires a port" $.name (default "" $rule.name) $pathVal) }} + {{- end }} + + {{- $paths = append $paths (dict + "path" $pathVal + "pathType" $pathType + "serviceName" $pathServiceName + "port" $pathPort + ) }} + {{- end }} + {{- end }} + {{- end }} + + {{- /* Only emit host entry if at least one path was produced */ -}} + {{- if gt (len $paths) 0 }} + {{- $hosts = append $hosts (dict "host" $host "paths" $paths) }} + {{- end }} + {{- end }} {{- else }} From 4293cb249238c64c2ffb5a33d9eae241ae55e715 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Wed, 17 Dec 2025 17:59:15 +0200 Subject: [PATCH 06/11] Remove checksum for configmap and secret from values since it will be genereted automatically --- angular-osf/values.yaml | 4 ++-- cos-common/templates/_deployment.tpl | 16 ++++++---------- osf-graveyvalet/values.yaml | 20 ++++++++------------ 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index 85313adb..55937f28 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -158,8 +158,8 @@ main: # ------- Pod Annotations ------- - podAnnotations: - checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" +# Checksum for configmap and secret will be added automatically + podAnnotations: [] # ------- Service configuration ------- diff --git a/cos-common/templates/_deployment.tpl b/cos-common/templates/_deployment.tpl index 15f020b0..961004b5 100644 --- a/cos-common/templates/_deployment.tpl +++ b/cos-common/templates/_deployment.tpl @@ -36,19 +36,15 @@ spec: template: metadata: labels: - {{- include "cos-common.podLabels" . | nindent 6 }} - {{- with .values.podLabels }} - {{ tpl (toYaml .) .root | nindent 6 }} + {{- include "cos-common.podLabels" . | nindent 8 }} + {{- with $vals.podLabels }} + {{- tpl (toYaml .) $.root | nindent 8 }} {{- end }} - annotations: - {{- /* User-defined pod annotations */}} - {{- with .values.podAnnotations }} - {{ tpl (toYaml .) .root | nindent 6 }} + {{- with $vals.podAnnotations }} + {{- tpl (toYaml .) $.root | nindent 8 }} {{- end }} - - {{- /* ConfigMap and secret checksum for rollout */}} - {{- include "cos-common.podChecksums" . | nindent 6 }} + {{- include "cos-common.podChecksums" . | nindent 8 }} spec: {{- /* Shared pod spec helper wires containers, volumes, TLS, affinities, etc. */}} {{- include "cos-common.podSpec" . | nindent 6 }} diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 8cbab651..14c39fd3 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -265,9 +265,8 @@ main: # ------- Pod Annotations ------- - podAnnotations: - checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" - checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" +# Checksum for configmap and secret will be added automatically + podAnnotations: [] # ------- Service configuration ------- # service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' @@ -532,9 +531,8 @@ worker: # ------- Pod Annotations ------- - podAnnotations: - checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" - checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" +# Checksum for configmap and secret will be added automatically + podAnnotations: [] # ------- HPA configuration ------- @@ -718,9 +716,8 @@ beat: # ------- Pod Annotations ------- - podAnnotations: - checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" - checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" +# Checksum for configmap and secret will be added automatically + podAnnotations: [] # ------- ConfigMap configuration ------- @@ -821,9 +818,8 @@ migration: # ------- Pod Annotations ------- - podAnnotations: - checksum/config: "{{ sha256sum (tpl (toYaml .Values.main.configMap.data) . ) }}" - checksum/secret: "{{ sha256sum (tpl (toYaml .Values.main.secret.data) . ) }}" +# Checksum for configmap and secret will be added automatically + podAnnotations: [] # ------- ConfigMap configuration ------- From b57c39e88c1e907fa8ba086ef180706f89e889b1 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Fri, 19 Dec 2025 13:00:25 +0200 Subject: [PATCH 07/11] Migrate osf-cas and osf-pigeon to cos-common library --- cos-common/templates/_helpers.tpl | 2 +- osf-cas/Chart.yaml | 22 +- osf-cas/requirements.lock | 9 - osf-cas/requirements.yaml | 9 - osf-cas/templates/NOTES.txt | 19 - osf-cas/templates/_helpers.tpl | 143 +++-- .../templates/certificate-networkpolicy.yaml | 20 - osf-cas/templates/certificate.yaml | 34 -- osf-cas/templates/configmap.yaml | 25 +- osf-cas/templates/deployment.yaml | 177 ------ osf-cas/templates/hpa.yaml | 19 - osf-cas/templates/ingress.yaml | 66 -- osf-cas/templates/main.yaml | 10 + osf-cas/templates/networkpolicy.yaml | 36 -- osf-cas/templates/pdb.yaml | 21 - osf-cas/templates/secret.yaml | 26 +- osf-cas/templates/service.yaml | 20 - osf-cas/values.yaml | 571 ++++++++++++------ osf-pigeon/Chart.yaml | 14 +- osf-pigeon/files/nginx.conf | 117 ++++ osf-pigeon/templates/NOTES.txt | 43 +- osf-pigeon/templates/_helpers.tpl | 55 -- .../templates/certificate-networkpolicy.yaml | 20 - osf-pigeon/templates/certificate.yaml | 34 -- osf-pigeon/templates/configmap.yaml | 137 ----- osf-pigeon/templates/deployment.yaml | 140 ----- osf-pigeon/templates/hpa.yaml | 19 - osf-pigeon/templates/ingress.yaml | 49 -- osf-pigeon/templates/main.yaml | 10 + osf-pigeon/templates/networkpolicy.yaml | 30 - osf-pigeon/templates/pdb.yaml | 21 - osf-pigeon/templates/secret.yaml | 17 - osf-pigeon/templates/service.yaml | 19 - osf-pigeon/values.yaml | 515 +++++++++++----- 34 files changed, 1060 insertions(+), 1409 deletions(-) delete mode 100644 osf-cas/requirements.lock delete mode 100644 osf-cas/requirements.yaml delete mode 100644 osf-cas/templates/NOTES.txt delete mode 100644 osf-cas/templates/certificate-networkpolicy.yaml delete mode 100644 osf-cas/templates/certificate.yaml delete mode 100644 osf-cas/templates/deployment.yaml delete mode 100644 osf-cas/templates/hpa.yaml delete mode 100644 osf-cas/templates/ingress.yaml create mode 100644 osf-cas/templates/main.yaml delete mode 100644 osf-cas/templates/networkpolicy.yaml delete mode 100644 osf-cas/templates/pdb.yaml delete mode 100644 osf-cas/templates/service.yaml create mode 100644 osf-pigeon/files/nginx.conf delete mode 100644 osf-pigeon/templates/_helpers.tpl delete mode 100644 osf-pigeon/templates/certificate-networkpolicy.yaml delete mode 100644 osf-pigeon/templates/certificate.yaml delete mode 100644 osf-pigeon/templates/configmap.yaml delete mode 100644 osf-pigeon/templates/deployment.yaml delete mode 100644 osf-pigeon/templates/hpa.yaml delete mode 100644 osf-pigeon/templates/ingress.yaml create mode 100644 osf-pigeon/templates/main.yaml delete mode 100644 osf-pigeon/templates/networkpolicy.yaml delete mode 100644 osf-pigeon/templates/pdb.yaml delete mode 100644 osf-pigeon/templates/secret.yaml delete mode 100644 osf-pigeon/templates/service.yaml diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl index 390edf2a..63cece88 100644 --- a/cos-common/templates/_helpers.tpl +++ b/cos-common/templates/_helpers.tpl @@ -26,7 +26,7 @@ Parse init-certs config (supports boolean for backward compatibility). {{- $enabled := default false $cfg.enabled -}} {{- $mount := $cfg.mountToContainer -}} {{- $owner := default "www-data:www-data" $cfg.userCertsOwner -}} -{{- dict "enabled" $enabled "mountToContainer" $mount "userCertsOwner" $owner -}} +{{- toYaml (dict "enabled" $enabled "mountToContainer" $mount "userCertsOwner" $owner) -}} {{- end }} {{/* diff --git a/osf-cas/Chart.yaml b/osf-cas/Chart.yaml index 9a113a94..a1f6d685 100644 --- a/osf-cas/Chart.yaml +++ b/osf-cas/Chart.yaml @@ -1,7 +1,8 @@ -apiVersion: v1 +apiVersion: v2 description: Central Authentication Service name: osf-cas -version: 0.2.0 +type: application +version: 1.0.0 keywords: - authentication sources: @@ -13,5 +14,18 @@ maintainers: - name: Matt Clark email: mattclark@cos.io url: https://github.com/mattclark -engine: gotpl -tillerVersion: '>=2.7.0' +dependencies: + - name: maintenance + version: 0.2.0 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: maintenance.enabled, global.maintenance.enabled + - name: postgresql + version: 0.11.1 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: postgresql.enabled, global.postgresql.enabled + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" diff --git a/osf-cas/requirements.lock b/osf-cas/requirements.lock deleted file mode 100644 index 67ba2e7a..00000000 --- a/osf-cas/requirements.lock +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: maintenance - repository: https://centerforopenscience.github.io/helm-charts/ - version: 0.2.0 -- name: postgresql - repository: https://centerforopenscience.github.io/helm-charts/ - version: 0.11.1 -digest: sha256:1954603e9d2ecfdd0c91d6e593aa4c70028b07f3672f86aa675979083fcd05b9 -generated: 2023-01-24T17:12:21.536895-05:00 diff --git a/osf-cas/requirements.yaml b/osf-cas/requirements.yaml deleted file mode 100644 index bf928ef7..00000000 --- a/osf-cas/requirements.yaml +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: - - name: maintenance - version: 0.2.0 - repository: https://centerforopenscience.github.io/helm-charts/ - condition: maintenance.enabled, global.maintenance.enabled - - name: postgresql - version: 0.11.1 - repository: https://centerforopenscience.github.io/helm-charts/ - condition: postgresql.enabled, global.postgresql.enabled diff --git a/osf-cas/templates/NOTES.txt b/osf-cas/templates/NOTES.txt deleted file mode 100644 index e3c91dca..00000000 --- a/osf-cas/templates/NOTES.txt +++ /dev/null @@ -1,19 +0,0 @@ -{{/* -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "cas.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "cas.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "cas.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "cas.fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} -{{- end }} -*/}} \ No newline at end of file diff --git a/osf-cas/templates/_helpers.tpl b/osf-cas/templates/_helpers.tpl index 0e40cd94..d7d3a2fa 100644 --- a/osf-cas/templates/_helpers.tpl +++ b/osf-cas/templates/_helpers.tpl @@ -1,57 +1,15 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "cas.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "cas.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "cas.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified maintenance name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "maintenance.fullname" -}} -{{- $name := "maintenance" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified postgresql name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "postgresql.fullname" -}} -{{- printf "%s-%s" .Release.Name "postgresql" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - {{/* Apache volume mounts */}} {{- define "cas.apache.volumeMounts" }} -{{- range $key := keys (merge .Values.apache.configFiles (include "cas.apache.inlineconfigs" . | fromYaml) (include "cas.apache.fileconfigs" . | fromYaml)) }} +{{- $configFiles := merge (default dict .Values.apache.configFiles) (include "cas.apache.inlineconfigs" . | fromYaml | default dict) (include "cas.apache.fileconfigs" . | fromYaml | default dict) }} +{{- range $key := keys $configFiles }} - mountPath: /etc/{{ $key }} name: config subPath: apache-{{ $key | replace "/" "-" }} readOnly: true {{- end }} -{{- range $key := keys .Values.apache.secretFiles }} +{{- range $key := keys (default dict .Values.apache.secretFiles) }} - mountPath: /etc/{{ $key }} name: secret subPath: apache-{{ $key | replace "/" "-" }} @@ -60,13 +18,14 @@ Apache volume mounts {{- end }} {{- define "cas.jetty.volumeMounts" }} -{{- range $key := keys (merge .Values.jetty.configFiles (include "cas.jetty.inlineconfigs" . | fromYaml) (include "cas.jetty.fileconfigs" . | fromYaml)) }} +{{- $configFiles := merge (default dict .Values.jetty.configFiles) (include "cas.jetty.inlineconfigs" . | fromYaml | default dict) (include "cas.jetty.fileconfigs" . | fromYaml | default dict) }} +{{- range $key := keys $configFiles }} - mountPath: /etc/cas/{{ $key }} name: config subPath: jetty-{{ $key | replace "/" "-" }} readOnly: true {{- end }} -{{- range $key := keys .Values.jetty.secretFiles }} +{{- range $key := keys (default dict .Values.jetty.secretFiles) }} - mountPath: /etc/cas/{{ $key }} name: secret subPath: jetty-{{ $key | replace "/" "-" }} @@ -78,15 +37,15 @@ Apache volume mounts Apache environment variables */}} {{- define "cas.apache.environment" -}} -{{- $fullname := include "cas.fullname" . -}} -{{- range $key := keys .Values.apache.configEnvs }} +{{- $fullname := include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) -}} +{{- range $key := keys (default dict .Values.apache.configEnvs) }} - name: {{ $key }} valueFrom: configMapKeyRef: name: {{ $fullname }} key: apache-{{ $key }} {{- end }} -{{- range $key := keys .Values.apache.secretEnvs }} +{{- range $key := keys (default dict .Values.apache.secretEnvs) }} - name: {{ $key }} valueFrom: secretKeyRef: @@ -112,15 +71,15 @@ Jetty environment variables name: {{ template "postgresql.fullname" . }} key: postgres-password {{- end }} -{{- $fullname := include "cas.fullname" . -}} -{{- range $key := keys .Values.jetty.configEnvs }} +{{- $fullname := include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) -}} +{{- range $key := keys (default dict .Values.jetty.configEnvs) }} - name: {{ $key }} valueFrom: configMapKeyRef: name: {{ $fullname }} key: jetty-{{ $key }} {{- end }} -{{- range $key := keys .Values.jetty.secretEnvs }} +{{- range $key := keys (default dict .Values.jetty.secretEnvs) }} - name: {{ $key }} valueFrom: secretKeyRef: @@ -128,3 +87,81 @@ Jetty environment variables key: jetty-{{ $key }} {{- end }} {{- end -}} + +{{/* +Checksums for rendered ConfigMap/Secret to trigger pod restarts on updates. +*/}} +{{- define "cas.podChecksums" -}} +checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} +checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} +{{- end }} + +{{/* +Prepare the main values with injected Jetty/Apache envs and volume mounts +*/}} +{{- define "cas.main.values" -}} +{{- $root := .root -}} +{{- $main := deepCopy (default dict .values) -}} + +{{- /* Inject Jetty envs from helpers */}} +{{- $jettyEnvRaw := include "cas.jetty.environment" $root | fromYamlArray }} +{{- $jettyEnv := ternary $jettyEnvRaw (list) (kindIs "slice" $jettyEnvRaw) }} +{{- $mergedEnv := list }} +{{- range (default list $main.env) }} + {{- $mergedEnv = append $mergedEnv . }} +{{- end }} +{{- range (default list $jettyEnv) }} + {{- $mergedEnv = append $mergedEnv . }} +{{- end }} +{{- $_ := set $main "env" $mergedEnv }} + +{{- /* Inject Jetty volume mounts from helpers */}} +{{- $jettyMountsRaw := include "cas.jetty.volumeMounts" $root | fromYamlArray }} +{{- $jettyMounts := ternary $jettyMountsRaw (list) (kindIs "slice" $jettyMountsRaw) }} +{{- $mergedMounts := list }} +{{- range (default list $main.additionalVolumeMounts) }} + {{- $mergedMounts = append $mergedMounts . }} +{{- end }} +{{- range (default list $jettyMounts) }} + {{- $mergedMounts = append $mergedMounts . }} +{{- end }} +{{- $_ := set $main "additionalVolumeMounts" $mergedMounts }} + +{{- /* Wire Apache helper envs/volume mounts into the apache additional container */}} +{{- $updatedContainers := list }} +{{- range $idx, $container := default list $main.additionalContainers }} + {{- $c := deepCopy $container }} + {{- if eq $c.name "apache" }} + {{- $apacheEnvRaw := include "cas.apache.environment" $root | fromYamlArray }} + {{- $apacheEnv := ternary $apacheEnvRaw (list) (kindIs "slice" $apacheEnvRaw) }} + {{- $cEnv := list }} + {{- range (default list $c.env) }} + {{- $cEnv = append $cEnv . }} + {{- end }} + {{- range (default list $apacheEnv) }} + {{- $cEnv = append $cEnv . }} + {{- end }} + {{- $_ := set $c "env" $cEnv }} + + {{- $apacheMountsRaw := include "cas.apache.volumeMounts" $root | fromYamlArray }} + {{- $apacheMounts := ternary $apacheMountsRaw (list) (kindIs "slice" $apacheMountsRaw) }} + {{- $cMounts := list }} + {{- range (default list $c.volumeMounts) }} + {{- $cMounts = append $cMounts . }} + {{- end }} + {{- range (default list $apacheMounts) }} + {{- $cMounts = append $cMounts . }} + {{- end }} + {{- $_ := set $c "volumeMounts" $cMounts }} + {{- end }} + {{- $updatedContainers = append $updatedContainers $c }} +{{- end }} +{{- $_ := set $main "additionalContainers" $updatedContainers }} + +{{- /* Add checksum pod annotations for config/secret changes. */}} +{{- $checksumAnnotations := include "cas.podChecksums" $root | fromYaml | default dict }} +{{- $podAnnotations := merge (dict) (default dict $main.podAnnotations) $checksumAnnotations }} +{{- $_ := set $main "podAnnotations" $podAnnotations }} + +{{- toYaml $main -}} +{{- end -}} diff --git a/osf-cas/templates/certificate-networkpolicy.yaml b/osf-cas/templates/certificate-networkpolicy.yaml deleted file mode 100644 index c58f2e83..00000000 --- a/osf-cas/templates/certificate-networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if (and .Values.networkPolicy.enabled .Values.certificate.enabled) }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: "{{ template "cas.certificate.fullname" . }}" - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - policyTypes: - - Ingress - podSelector: - matchExpressions: - - {key: acme.cert-manager.io/http01-solver, operator: Exists} - ingress: - - from: [] -{{- end }} diff --git a/osf-cas/templates/certificate.yaml b/osf-cas/templates/certificate.yaml deleted file mode 100644 index 4253f53e..00000000 --- a/osf-cas/templates/certificate.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if and .Values.certificate.enabled .Values.certificate.createCert -}} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "{{ template "cas.certificate.fullname" . }}" - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - secretName: "{{ template "cas.certificate.fullname" . }}" - issuerRef: - name: {{ .Values.certificate.issuerRef.name }} - kind: {{ .Values.certificate.issuerRef.kind }} - commonName: {{ .Values.certificate.commonName }} - dnsNames: - {{- range .Values.certificate.dnsNames }} - - {{ . }} - {{- end }} - acme: - config: - - http01: - {{- if hasKey .Values.certificate.acmeConfig.http01 "ingress" }} - ingress: {{ .Values.certificate.acmeConfig.http01.ingress }} - {{- else }} - ingress: {{ template "cas.fullname" . }} - {{- end }} - domains: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} -{{- end -}} diff --git a/osf-cas/templates/configmap.yaml b/osf-cas/templates/configmap.yaml index 4b69911d..e20b25aa 100644 --- a/osf-cas/templates/configmap.yaml +++ b/osf-cas/templates/configmap.yaml @@ -1,12 +1,13 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "cas.fullname" . }} + name: {{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) | trim }} labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} + app.kubernetes.io/name: {{ .Chart.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: cas + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ printf "%s-%s" .Chart.Name (replace "+" "_" .Chart.Version) }} data: {{- define "cas.jetty.preprint-services" }} africarxiv: @@ -261,7 +262,7 @@ shibboleth/shibboleth2.xml: |- httpd/conf.d/shib.conf: |- LoadModule mod_shib /usr/lib64/shibboleth/mod_shib_24.so - + ServerName health LoadModule status_module "modules/mod_status.so" @@ -272,7 +273,7 @@ httpd/conf.d/shib.conf: |- SetHandler server-status - + ServerName https://{{ required "casDomain must be set" .Values.casDomain }}:443 UseCanonicalName On ServerAdmin admin@osf.io @@ -297,8 +298,8 @@ httpd/conf.d/shib.conf: |- # ShibDisable on ShibRequestSetting REMOTE_ADDR X-Forwarded-For - ProxyPass http://127.0.0.1:{{ .Values.service.externalPort }}/ - ProxyPassReverse http://127.0.0.1:{{ .Values.service.externalPort }}/ + ProxyPass http://127.0.0.1:{{ .Values.main.http.containers.apache.externalPort }}/ + ProxyPassReverse http://127.0.0.1:{{ .Values.main.http.containers.apache.externalPort }}/ @@ -311,8 +312,8 @@ httpd/conf.d/shib.conf: |- ShibUseEnvironment off ShibUseHeaders on - ProxyPass http://127.0.0.1:{{ .Values.service.externalPort }}/login - ProxyPassReverse http://127.0.0.1:{{ .Values.service.externalPort }}/login + ProxyPass http://127.0.0.1:{{ .Values.main.http.containers.apache.externalPort }}/login + ProxyPassReverse http://127.0.0.1:{{ .Values.main.http.containers.apache.externalPort }}/login @@ -1026,4 +1027,4 @@ services/preprints-{{ $key }}-{{ $val.id }}.json: |- {{- range $key, $value := merge .Values.jetty.configFiles (include "cas.jetty.inlineconfigs" . | fromYaml) (include "cas.jetty.fileconfigs" . | fromYaml) }} jetty-{{ $key | replace "/" "-" }}: |- {{- $value | nindent 4 }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/osf-cas/templates/deployment.yaml b/osf-cas/templates/deployment.yaml deleted file mode 100644 index e659d2fe..00000000 --- a/osf-cas/templates/deployment.yaml +++ /dev/null @@ -1,177 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "cas.fullname" . }} - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - replicas: {{ .Values.replicaCount }} - {{- if .Values.strategy }} - strategy: - {{- toYaml .Values.strategy | nindent 4 }} - {{- end }} - template: - metadata: - labels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - annotations: - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} - {{- if and (eq .Capabilities.KubeVersion.Major "1") (lt .Capabilities.KubeVersion.Minor "8") }} - pod.alpha.kubernetes.io/init-containers: null - pod.beta.kubernetes.io/init-containers: null - {{- end }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - {{- end }} - {{- if .Values.tls.enabled }} - initContainers: - - name: certificates - image: "{{ .Values.jetty.image.repository }}:{{ .Values.jetty.image.tag }}" - imagePullPolicy: {{ .Values.jetty.image.pullPolicy }} - securityContext: - runAsUser: 0 - command: - - /bin/sh - - -c - - |- - set -ex - export dir=/root/.postgresql - cp -f /certs/* ${dir} - chmod 0700 ${dir} - chmod -R 0600 ${dir}/* - volumeMounts: - - mountPath: /root/.postgresql - name: certs - {{- range $key := keys .Values.tls.files }} - - mountPath: /certs/{{ $key }} - name: secret - subPath: certs-{{ $key }} - readOnly: true - {{- end }} - {{- range $key := keys .Values.tls.base64Files }} - - mountPath: /certs/{{ $key }} - name: secret - subPath: certs-{{ $key }} - readOnly: true - {{- end }} - {{- end }} - containers: - - name: apache - image: "{{ .Values.apache.image.repository }}:{{ .Values.apache.image.tag }}" - im: {{ .Values.apache.image.pullPolicy }} - env: - {{- include "cas.apache.environment" . | nindent 12 }} - ports: - - name: http - containerPort: {{ .Values.service.internalPort }} - livenessProbe: - httpGet: - httpHeaders: - - name: Host - value: health - path: /healthz - port: {{ .Values.service.internalPort }} - initialDelaySeconds: 300 - timeoutSeconds: {{ default "1" .Values.apache.livenessProbe.timeoutSeconds }} - periodSeconds: {{ default "10" .Values.apache.livenessProbe.periodSeconds }} - successThreshold: {{ default "1" .Values.apache.livenessProbe.successThreshold }} - failureThreshold: {{ default "10" .Values.apache.livenessProbe.failureThreshold }} - readinessProbe: - httpGet: - httpHeaders: - - name: Host - value: {{ .Values.casDomain }} - path: /Shibboleth.sso/Status - port: {{ .Values.service.internalPort }} - initialDelaySeconds: 10 - timeoutSeconds: {{ default "1" .Values.apache.readinessProbe.timeoutSeconds }} - periodSeconds: {{ default "10" .Values.apache.readinessProbe.periodSeconds }} - successThreshold: {{ default "1" .Values.apache.readinessProbe.successThreshold }} - failureThreshold: {{ default "3" .Values.apache.readinessProbe.failureThreshold }} - resources: - {{- toYaml .Values.apache.resources | nindent 12 }} - volumeMounts: - {{- include "cas.apache.volumeMounts" . | nindent 12 }} - - name: jetty - image: "{{ .Values.jetty.image.repository }}:{{ .Values.jetty.image.tag }}" - imagePullPolicy: {{ .Values.jetty.image.pullPolicy }} - env: - {{- include "cas.jetty.environment" . | nindent 12 }} - ports: - - name: http - containerPort: {{ .Values.service.externalPort }} - livenessProbe: - httpGet: - path: {{ default "/login" .Values.jetty.livenessProbe.path}} - port: {{ .Values.service.externalPort }} - initialDelaySeconds: 300 - timeoutSeconds: {{ default "1" .Values.jetty.livenessProbe.timeoutSeconds }} - periodSeconds: {{ default "10" .Values.jetty.livenessProbe.periodSeconds }} - successThreshold: {{ default "1" .Values.jetty.livenessProbe.successThreshold }} - failureThreshold: {{ default "10" .Values.jetty.livenessProbe.failureThreshold }} - readinessProbe: - httpGet: - path: {{ default "/login" .Values.jetty.readinessProbe.path }} - port: {{ .Values.service.externalPort }} - initialDelaySeconds: 10 - timeoutSeconds: {{ default "1" .Values.jetty.readinessProbe.timeoutSeconds }} - periodSeconds: {{ default "10" .Values.jetty.readinessProbe.periodSeconds }} - successThreshold: {{ default "1" .Values.jetty.readinessProbe.successThreshold }} - failureThreshold: {{ default "3" .Values.jetty.readinessProbe.failureThreshold }} - resources: - {{- toYaml .Values.jetty.resources | nindent 12 }} - volumeMounts: - - mountPath: /etc/cas - name: cas - {{- if .Values.tls.enabled }} - - mountPath: /root/.postgresql - name: certs - {{- end }} - {{- include "cas.jetty.volumeMounts" . | nindent 12 }} - volumes: - - name: cas - emptyDir: {} - - name: config - configMap: - name: {{ template "cas.fullname" . }} - - name: secret - secret: - secretName: {{ template "cas.fullname" . }} - {{- if .Values.tls.enabled }} - - name: certs - emptyDir: {} - {{- end }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} diff --git a/osf-cas/templates/hpa.yaml b/osf-cas/templates/hpa.yaml deleted file mode 100644 index 19b14a32..00000000 --- a/osf-cas/templates/hpa.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "cas.fullname" . }} - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "cas.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} diff --git a/osf-cas/templates/ingress.yaml b/osf-cas/templates/ingress.yaml deleted file mode 100644 index 23dac989..00000000 --- a/osf-cas/templates/ingress.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "cas.fullname" . }} - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- if .Values.maintenance.enabled }} - {{- $serviceName := include "maintenance.fullname" . -}} - {{- $servicePort := .Values.maintenance.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- else -}} - {{- $serviceName := include "cas.fullname" . -}} - {{- $servicePort := .Values.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls)) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "cas.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- end -}} -{{- end -}} diff --git a/osf-cas/templates/main.yaml b/osf-cas/templates/main.yaml new file mode 100644 index 00000000..c53bdd44 --- /dev/null +++ b/osf-cas/templates/main.yaml @@ -0,0 +1,10 @@ +{{- $root := . -}} +{{- $main := include "cas.main.values" (dict "root" $root "values" .Values.main) | fromYaml }} + +{{- include "cos-common.certificate" (dict "root" $root "name" "cert" "values" $main) }} +{{- include "cos-common.deployment" (dict "root" $root "name" "" "values" $main) }} +{{- include "cos-common.service" (dict "root" $root "name" "" "values" $main) }} +{{- include "cos-common.ingress" (dict "root" $root "name" "" "values" $main) }} +{{- include "cos-common.hpa" (dict "root" $root "name" "" "values" $main) }} +{{- include "cos-common.pdb" (dict "root" $root "name" "" "values" $main) }} +{{- include "cos-common.networkpolicy" (dict "root" $root "name" "" "values" $main) }} diff --git a/osf-cas/templates/networkpolicy.yaml b/osf-cas/templates/networkpolicy.yaml deleted file mode 100644 index 7e115c56..00000000 --- a/osf-cas/templates/networkpolicy.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "cas.fullname" . }} - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - podSelector: - matchLabels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - ingress: - - ports: - - port: {{ .Values.service.internalPort }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "cas.fullname" . }}-client: "true" - {{- end }} - egress: - {{- if .Values.postgresql.enabled }} - - to: - - podSelector: - matchLabels: - {{ template "cas.fullname" . }}-client: "true" - ports: - - port: 5432 - protocol: TCP - {{- end }} - {{- toYaml .Values.networkPolicy.egress | nindent 4 }} -{{- end }} diff --git a/osf-cas/templates/pdb.yaml b/osf-cas/templates/pdb.yaml deleted file mode 100644 index cbf08fad..00000000 --- a/osf-cas/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "cas.fullname" . }}" - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/osf-cas/templates/secret.yaml b/osf-cas/templates/secret.yaml index 35ffbeba..96e2fa63 100644 --- a/osf-cas/templates/secret.yaml +++ b/osf-cas/templates/secret.yaml @@ -1,12 +1,13 @@ apiVersion: v1 kind: Secret metadata: - name: {{ template "cas.fullname" . }} + name: {{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) | trim }} labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} + app.kubernetes.io/name: {{ .Chart.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: {{ .Chart.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ printf "%s-%s" .Chart.Name (replace "+" "_" .Chart.Version) }} type: Opaque data: {{- range $key, $value := .Values.apache.secretEnvs }} @@ -24,10 +25,15 @@ data: {{- end }} {{- if .Values.tls.enabled }} - {{- range $key, $value := .Values.tls.files }} - certs-{{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- range $key, $value := .Values.tls.base64Files }} - certs-{{ $key }}: {{ $value | nospace | quote }} + {{- /* Inject TLS certs from any enabled tls. block so cos-common mounts match subPaths. */ -}} + {{- range $app, $cfg := omit .Values.tls "enabled" }} + {{- if and $cfg (kindIs "map" $cfg) (default false $cfg.enabled) }} + {{- range $key, $value := (default dict $cfg.files) }} + certs-{{ $app }}-{{ $key }}: {{ $value | b64enc | quote }} + {{- end }} + {{- range $key, $value := (default dict $cfg.base64Files) }} + certs-{{ $app }}-{{ $key }}: {{ $value | nospace | quote }} + {{- end }} + {{- end }} {{- end }} {{- end }} diff --git a/osf-cas/templates/service.yaml b/osf-cas/templates/service.yaml deleted file mode 100644 index 85629700..00000000 --- a/osf-cas/templates/service.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "cas.fullname" . }} - labels: - app: {{ template "cas.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - sessionAffinity: {{ .Values.service.sessionAffinity }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "cas.name" . }} - release: {{ .Release.Name }} diff --git a/osf-cas/values.yaml b/osf-cas/values.yaml index 71b163f3..3d42f594 100644 --- a/osf-cas/values.yaml +++ b/osf-cas/values.yaml @@ -1,199 +1,17 @@ -# Default values for cas. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 - -antiAffinity: soft - -# strategy: -# rollingUpdate: -# maxSurge: 25% -# maxUnavailable: 25% -# type: RollingUpdate - -budget: - minAvailable: 0 - -apache: - image: - repository: quay.io/centerforopenscience/shibboleth-sp - tag: latest - pullPolicy: Always - livenessProbe: {} - readinessProbe: {} - resources: {} - #limits: - # cpu: 1 - # memory: 128Mi - #requests: - # cpu: 100m - # memory: 128Mi - configEnvs: {} - configFiles: {} - secretEnvs: {} - secretFiles: {} - # shibboleth/incommon-idp-signature.pem: |- - - # shibboleth/sp-cert.pem: |- - - # shibboleth/sp-key.pem: |- - - # shibboelth/shibboleth2.xml: |- - - -jetty: - image: - repository: quay.io/centerforopenscience/cas - tag: develop - pullPolicy: Always - livenessProbe: {} - readinessProbe: {} - resources: {} - #limits: - # cpu: 1 - # memory: 128Mi - #requests: - # cpu: 100m - # memory: 128Mi - configEnvs: - JAVA_OPTIONS: -Xms512m -Xmx512m - configFiles: {} - secretEnvs: - OAUTH_ORCID_CLIENT_ID: '' - OAUTH_ORCID_CLIENT_SECRET: '' - OSF_DB_URL: 'jdbc:postgresql://osf-postgresql.namespace/osf?targetServerType=master' - OSF_DB_USER: '' - OSF_DB_PASSWORD: '' - OSF_JWE_SECRET: '' - OSF_JWT_SECRET: '' - TGC_ENCRYPTION_KEY: '' - TGC_SIGNING_KEY: '' - ## If postgresql.enabled is false - # DATABASE_URL: '' - # DATABASE_USER: '' - # DATABASE_PASSWORD: '' - secretFiles: {} - -horizontalPodAutoscaler: - enabled: false - maxReplicas: 3 - targetCPUUtilizationPercentage: 90 - -service: - name: cas - type: ClusterIP - sessionAffinity: None - externalPort: 8080 - internalPort: 80 - -ingress: - enabled: false - # Used to create Ingress record (should used with service.type: ClusterIP). - hosts: - - chart-example.local - paths: - - / - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - tls: - # Secrets must be manually created in the namespace. - # - secretName: chart-example-tls - # hosts: - # - chart-example.local -casDomain: 'staging-accounts.osf.io' -osfDomain: 'staging.osf.io' -apiDomain: 'staging-api.osf.io' -preprintProviderDomains: - override: true - prefix: staging- - suffix: .cos.io - -certificate: - enabled: false - createCert: false - name: cert - # WORKAROUND: Ingress deploy blocked to GLBC due to race condition w/ missing TLS certificate - # - Issue: https://github.com/jetstack/cert-manager/issues/606 - # - PR: https://github.com/kubernetes/ingress-gce/pull/388 - tls: true - # issuerRef: - # name: letsencrypt-prod - # kind: ClusterIssuer - # commonName: example.org - # dnsNames: - # - example.org - # - subdomain.example.org - # acmeConfig: - # http01: {} - # # ingress: '' - # domains: - # - example.org - # - subdomain.example.org - -networkPolicy: - enabled: false - # Allows external access to the pod, otherwise access is restricted to - # clients with the explicit label. - allowExternal: true - egress: {} - # - to: - # - namespaceSelector: {} - # ports: - # - port: 53 # dns - # protocol: TCP - # - port: 53 # dns - # protocol: UDP - # - to: - # - ipBlock: - # cidr: 0.0.0.0/0 - # except: - # - 10.0.0.0/8 - # - 172.16.0.0/12 - # - 192.168.0.0/16 - # ports: - # - port: 80 # http - # protocol: TCP - # - port: 443 # https - # protocol: TCP - -### https://sentry.cos.io/sentry/{project}/settings/keys/ -sentryDSN: '' - -# Database connection TLS -tls: - enabled: false - files: - # Root Certificate - root.crt: |- - - # CAS database certificate - postgresql.crt: |- - - # OSF database certificate - osf.crt: |- - - # Files already base64 encoded - base64Files: - # CAS database key (PK8 DER Format, must be named postgresql.pk8 unless otherwise specified) - postgresql.pk8: '' - # OSF database key (PK8 DER Format) - osf.pk8: '' +### ---------- Global or Reusable parts across values.yaml ---------- maintenance: enabled: false + service: + externalPort: 8080 postgresql: - enabled: false + enabled: false # do not forget tu update networkPolicy ## image: ## repository: postgres image repository ## tag: postgres image version ## pullPolicy: Always ## ref: https://hub.docker.com/r/library/postgres/tags/ - # - ## Specify a imagePullPolicy - ## 'Always' if imageTag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## Create a database user ## Default: postgres @@ -234,3 +52,384 @@ postgresql: requests: memory: 256Mi cpu: 100m + +# Domain defaults (used in templated service definitions) +casDomain: staging-accounts.osf.io +osfDomain: staging.osf.io +apiDomain: staging-api.osf.io +preprintProviderDomains: + override: true + prefix: staging- + suffix: .cos.io + +apache: + configEnvs: {} + configFiles: {} + secretEnvs: {} + secretFiles: {} + +jetty: + configEnvs: + JAVA_OPTIONS: -Xms512m -Xmx512m + configFiles: {} + secretEnvs: + OAUTH_ORCID_CLIENT_ID: '' + OAUTH_ORCID_CLIENT_SECRET: '' + OSF_DB_URL: 'jdbc:postgresql://osf-postgresql.namespace/osf?targetServerType=master' + OSF_DB_USER: '' + OSF_DB_PASSWORD: '' + OSF_JWE_SECRET: '' + OSF_JWT_SECRET: '' + TGC_ENCRYPTION_KEY: '' + TGC_SIGNING_KEY: '' + ## If postgresql.enabled is false + # DATABASE_URL: '' + # DATABASE_USER: '' + # DATABASE_PASSWORD: '' + secretFiles: {} + +# Database connection TLS +tls: + enabled: false + postgresql: + enabled: false + mountPath: /root/.postgresql + files: + # Root Certificate + root.crt: |- + + # CAS database certificate + postgresql.crt: |- + + # OSF database certificate + osf.crt: |- + + # Files already base64 encoded + base64Files: + # CAS database key (PK8 DER Format, must be named postgresql.pk8 unless otherwise specified) + postgresql.pk8: "" + # OSF database key (PK8 DER Format) + osf.pk8: "" + + +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name + +## =============== MAIN Component =============== +main: + + enabled: true + + replicas: 1 + + http: + containers: + apache: + internalPort: 80 + externalPort: 8080 + serviceType: ClusterIP + +# ------- Configuration follows for containerName: jetty ------- + image: + repository: quay.io/centerforopenscience/cas + tag: develop + pullPolicy: Always + + containerName: jetty + + env: [] + + envFrom: [] + + probes: + liveness: + httpGet: + path: /login + port: "{{ .Values.main.http.containers.apache.externalPort }}" + initialDelaySeconds: 300 + timeoutSeconds: 1 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 + + readiness: + httpGet: + path: /login + port: "{{ .Values.main.http.containers.apache.externalPort }}" + initialDelaySeconds: 10 + timeoutSeconds: 1 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + + ports: + - name: http + containerPort: "{{ .Values.main.http.containers.apache.externalPort }}" + protocol: TCP + + volumeMounts: + - name: cas + mountPath: /etc/cas + + additionalVolumeMounts: [] + + resources: {} + + +# ------- Init containers ------- + initContainers: [] + + additionalInitContainers: [] + + # will add init containers which change permissions for TLS certs and mounts certs to the target container + enabledInitContainersCertificate: + enabled: "{{ .Values.tls.enabled }}" + mountToContainer: jetty + userCertsOwner: root:root + + +# ------- Additional containers ------- + apache: + image: + repository: quay.io/centerforopenscience/shibboleth-sp + tag: latest + pullPolicy: Always + resources: + limits: + cpu: 1 + memory: 512Mi + requests: + cpu: 10m + memory: 256Mi + # ephemeral-storage: 10Gi + + additionalContainers: + - name: apache + image: "{{ .Values.main.apache.image.repository }}:{{ .Values.main.apache.image.tag }}" + imagePullPolicy: "{{ .Values.main.apache.image.pullPolicy }}" + env: [] + envFrom: [] + ports: + - name: http-internal + containerPort: "{{ .Values.main.http.containers.apache.internalPort }}" + readinessProbe: + httpGet: + path: /Shibboleth.sso/Status + port: "{{ .Values.main.http.containers.apache.internalPort }}" + httpHeaders: + - name: Host + value: "{{ .Values.casDomain }}" + initialDelaySeconds: 10 + timeoutSeconds: 1 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /healthz + port: "{{ .Values.main.http.containers.apache.internalPort }}" + httpHeaders: + - name: Host + value: health + initialDelaySeconds: 300 + timeoutSeconds: 1 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 + resources: + limits: + cpu: "{{ .Values.main.apache.resources.limits.cpu }}" + memory: "{{ .Values.main.apache.resources.limits.memory }}" + requests: + cpu: "{{ .Values.main.apache.resources.requests.cpu }}" + memory: "{{ .Values.main.apache.resources.requests.memory }}" + # ephemeral-storage: "{{ index .Values.main.apache.resources.requests \"ephemeral-storage\" }}" + volumeMounts: [] + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: cas + emptyDir: {} + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) | trim }}' + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) | trim }}' + + additionalVolumes: [] + + +# ------- Affitnity configuration ------- + affinity: {} + # podAntiAffinity: + # # Soft + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 1 + # podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave component name in main.yaml empty. + # Hard + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave component name in main.yaml empty. + + additionalAffinities: [] + + +# ------- Pod Annotations ------- + podAnnotations: [] + + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + service: + enabled: true + type: "{{ .Values.main.http.containers.apache.serviceType }}" + sessionAffinity: None + ports: + - name: http + port: "{{ .Values.main.http.containers.apache.externalPort }}" + targetPort: "{{ .Values.main.http.containers.apache.internalPort }}" + protocol: TCP + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.apache.externalPort }}" + paths: + - / + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local + + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + acmeConfig: + http01: + ingress: 'test' + domains: + - example.org + + # additionalCertificates: + # # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name + # - name: example-org-cert + # enabled: false + # # secretName: secret-with-cert + # commonName: example.org + # dnsNames: + # - example.org + # - submdomain.example.org + # issuerRef: + # name: letsencrypt-prod + # kind: ClusterIssuer + # acmeConfig: + # http01: {} + # # ingress: '' + # domains: + # - example.org + # - subdomain.example.org + + + # ---------- HPA configuration ---------- + hpa: + enabled: false + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + behavior: {} + + + # ---------- PDB configuration ---------- + pdb: + enabled: false + minAvailable: 0 + + +# ------- Network Policy configuration ------- +# Network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + networkPolicy: + enabled: false + componentScoped: true # this network policy will be applied to all components + ingressRules: + - ports: + - port: "{{ .Values.main.http.containers.apache.internalPort }}" + from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "client") | trim }}': "true" + egressRules: + - to: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "client") | trim }}': "true" + ports: + - port: 5432 + protocol: TCP + +# Additional network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalNetworkPolicies: + - name: cert-solver + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- Secrets configuration ------- +# Secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +# +# It is important to set secret.enabled to false when you have enabledInitContainersCertificate for the component but secret object +# created not by cos-common library. +# + secret: + enabled: false + + +# ------- Selectors and etc. ------- + nodeSelector: {} diff --git a/osf-pigeon/Chart.yaml b/osf-pigeon/Chart.yaml index 7e1ae299..71c23d0e 100644 --- a/osf-pigeon/Chart.yaml +++ b/osf-pigeon/Chart.yaml @@ -1,9 +1,17 @@ -apiVersion: v1 +apiVersion: v2 description: A Helm chart for Kubernetes name: osf-pigeon -version: 0.1.0 +type: application +version: 1.0.0 sources: - https://github.com/CenterForOpenScience/osf-pigeon/ +dependencies: + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" maintainers: - name: Matt Frazier email: matt@cos.io @@ -11,5 +19,3 @@ maintainers: - name: Matt Clark email: mattclark@cos.io url: https://github.com/mattclark -engine: gotpl -tillerVersion: '>=2.7.0' diff --git a/osf-pigeon/files/nginx.conf b/osf-pigeon/files/nginx.conf new file mode 100644 index 00000000..66997af7 --- /dev/null +++ b/osf-pigeon/files/nginx.conf @@ -0,0 +1,117 @@ +user nginx; +worker_processes {{ .Values.main.nginx.workerCount }}; + +load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; +{{- if .Values.main.nginx.vts.enabled }} +load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; +load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; +{{- end }} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + access_log /var/log/nginx/access.log main; + + real_ip_header {{ .Values.main.nginx.realIpHeader }}; + real_ip_recursive {{ .Values.main.nginx.realIpRecursive }}; + {{- range .Values.main.nginx.proxySourceRanges }} + set_real_ip_from {{ . }}; + {{- end }} + + {{- if .Values.main.nginx.vts.enabled }} + geoip_country /etc/nginx/GeoIP.dat; + geoip_city /etc/nginx/GeoLiteCity.dat; + geoip_proxy_recursive on; + {{- range .Values.main.nginx.proxySourceRanges }} + geoip_proxy {{ . }}; + {{- end }} + + vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.main.nginx.vts.statusZoneSize }}; + vhost_traffic_status_filter_by_set_key {{ .Values.main.nginx.vts.defaultFilterKey }}; + {{- end }} + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 620s; + keepalive_requests 10000; + types_hash_max_size 2048; + server_tokens off; + + gzip on; + gzip_proxied any; + gzip_disable "msie6"; + gzip_min_length 1400; + gzip_vary on; + gzip_buffers 4 32k; + gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + brotli on; + brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + {{- if .Values.main.nginx.vts.enabled }} + server { + listen {{ .Values.main.nginx.vts.internalPort }}; + server_name _; + + location /healthz { + access_log off; + return 200; + } + + location /nginx_status { + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + } + {{- end }} + + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + keepalive_timeout 620s; + client_max_body_size 25M; + server_name _; + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /healthz { + access_log off; + return 200; + } + + location = /robots.txt { + alias /usr/share/nginx/html/robots.txt; + } + + location / { + # Disable caching of application requests + add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; + add_header Expires "-1"; + add_header Pragma "no-cache"; + + # Mitigate HTTPoxy Vulnerability + # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ + proxy_set_header Proxy ""; + + proxy_buffering off; + proxy_request_buffering off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:{{ .Values.main.http.containers.nginx.externalPort }}; + } + } +} \ No newline at end of file diff --git a/osf-pigeon/templates/NOTES.txt b/osf-pigeon/templates/NOTES.txt index 5aa8956c..32e3215c 100644 --- a/osf-pigeon/templates/NOTES.txt +++ b/osf-pigeon/templates/NOTES.txt @@ -1,17 +1,28 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "osf-pigeon.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "osf-pigeon.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "osf-pigeon.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "osf-pigeon.fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +Component fullname: {{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} + +{{- if and .Values.main.ingress.enabled (.Values.main.ingress.hosts) }} +Ingress hosts: +{{- $hosts := list }} +{{- if and (kindIs "map" .Values.main.ingress.hosts) (or (hasKey .Values.main.ingress.hosts "primary") (hasKey .Values.main.ingress.hosts "secondary")) }} + {{- range $h := (default (list) .Values.main.ingress.hosts.primary) }} + {{- $hosts = append $hosts $h }} + {{- end }} + {{- range $h := (default (list) .Values.main.ingress.hosts.secondary) }} + {{- $hosts = append $hosts $h }} + {{- end }} +{{- else }} + {{- range $h := .Values.main.ingress.hosts }} + {{- $hosts = append $hosts $h.host }} + {{- end }} +{{- end }} +{{- range $hosts }} + - {{ . }} +{{- end }} +{{- else }} +Service ports: +{{- range .Values.main.service.ports }} + - {{ .name }}: {{ .port }} +{{- end }} +Port-forward example: +kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "cos-common.fullname" (dict "root" . "name" "" "values" .Values.main) }} 8080:{{ (index .Values.main.service.ports 0).port }} {{- end }} diff --git a/osf-pigeon/templates/_helpers.tpl b/osf-pigeon/templates/_helpers.tpl deleted file mode 100644 index 804fafd9..00000000 --- a/osf-pigeon/templates/_helpers.tpl +++ /dev/null @@ -1,55 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "osf-pigeon.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-pigeon.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "osf-pigeon.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Overridable deployment annotations -*/}} -{{- define "osf-pigeon.deploymentAnnotations" -}} -checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} -{{- end -}} - -{{- define "osf-pigeon.environment" -}} -{{- $fullname := include "osf-pigeon.fullname" . -}} -{{- range $key, $value := .Values.configEnvs }} -- name: {{ $key }} - valueFrom: - configMapKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- range $key, $value := .Values.secretEnvs }} -- name: {{ $key }} - valueFrom: - secretKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- if and .Values.persistence.enabled .Values.persistence.mountPath }} -- name: PIGEON_FILESTORE_DIR - value: {{ .Values.persistence.mountPath }} -{{- end }} -{{- end -}} diff --git a/osf-pigeon/templates/certificate-networkpolicy.yaml b/osf-pigeon/templates/certificate-networkpolicy.yaml deleted file mode 100644 index aa0d07e1..00000000 --- a/osf-pigeon/templates/certificate-networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if (and .Values.networkPolicy.enabled .Values.certificate.enabled) }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: "{{ template "osf-pigeon.certificate.fullname" . }}" - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - policyTypes: - - Ingress - podSelector: - matchExpressions: - - {key: acme.cert-manager.io/http01-solver, operator: Exists} - ingress: - - from: [] -{{- end }} diff --git a/osf-pigeon/templates/certificate.yaml b/osf-pigeon/templates/certificate.yaml deleted file mode 100644 index 83115ecb..00000000 --- a/osf-pigeon/templates/certificate.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.certificate.enabled -}} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "{{ template "osf-pigeon.certificate.fullname" . }}" - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - secretName: "{{ template "osf-pigeon.certificate.fullname" . }}" - issuerRef: - name: {{ .Values.certificate.issuerRef.name }} - kind: {{ .Values.certificate.issuerRef.kind }} - commonName: {{ .Values.certificate.commonName }} - dnsNames: - {{- range .Values.certificate.dnsNames }} - - {{ . }} - {{- end }} - acme: - config: - - http01: - {{- if hasKey .Values.certificate.acmeConfig.http01 "ingress" }} - ingress: {{ .Values.certificate.acmeConfig.http01.ingress }} - {{- else }} - ingress: {{ template "osf-pigeon.fullname" . }} - {{- end }} - domains: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} -{{- end -}} diff --git a/osf-pigeon/templates/configmap.yaml b/osf-pigeon/templates/configmap.yaml deleted file mode 100644 index cb86b386..00000000 --- a/osf-pigeon/templates/configmap.yaml +++ /dev/null @@ -1,137 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: - {{- define "osf-pigeon.inlineconfigs" }} -nginx.conf: |- - user nginx; - worker_processes {{ .Values.nginx.workerCount }}; - - load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; - {{- if .Values.nginx.vts.enabled }} - load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; - load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; - {{- end }} - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - access_log /var/log/nginx/access.log main; - - real_ip_header {{ .Values.nginx.realIpHeader }}; - real_ip_recursive {{ .Values.nginx.realIpRecursive }}; - {{- range .Values.nginx.proxySourceRanges }} - set_real_ip_from {{ . }}; - {{- end }} - - {{- if .Values.nginx.vts.enabled }} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - {{- range .Values.nginx.proxySourceRanges }} - geoip_proxy {{ . }}; - {{- end }} - - vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; - {{- end }} - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 620s; - keepalive_requests 10000; - types_hash_max_size 2048; - server_tokens off; - - gzip on; - gzip_proxied any; - gzip_disable "msie6"; - gzip_min_length 1400; - gzip_vary on; - gzip_buffers 4 32k; - gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - brotli on; - brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - {{- if .Values.nginx.vts.enabled }} - server { - listen {{ .Values.nginx.vts.internalPort }}; - server_name _; - - location /healthz { - access_log off; - return 200; - } - - location /nginx_status { - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - } - } - {{- end }} - - server { - listen {{ .Values.service.internalPort }}; - keepalive_timeout 620s; - client_max_body_size 25M; - server_name _; - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /healthz { - access_log off; - return 200; - } - - location = /robots.txt { - alias /usr/share/nginx/html/robots.txt; - } - - location / { - # Disable caching of application requests - add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; - add_header Expires "-1"; - add_header Pragma "no-cache"; - - # Mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - proxy_buffering off; - proxy_request_buffering off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://127.0.0.1:{{ .Values.service.externalPort }}; - } - } - } -{{- end -}} - {{- range $key, $value := .Values.configEnvs }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- range $key, $value := merge .Values.configFiles (include "osf-pigeon.inlineconfigs" . | fromYaml) ((.Files.Glob "files/*").AsConfig | fromYaml) }} - {{ $key }}: |- - {{- $value | nindent 4 }} - {{- end }} diff --git a/osf-pigeon/templates/deployment.yaml b/osf-pigeon/templates/deployment.yaml deleted file mode 100644 index ba78ca1a..00000000 --- a/osf-pigeon/templates/deployment.yaml +++ /dev/null @@ -1,140 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - replicas: {{ .Values.replicaCount }} - {{- if .Values.strategy }} - strategy: - {{- toYaml .Values.strategy | nindent 4 }} - {{- end }} - template: - metadata: - labels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - annotations: - {{- include "osf-pigeon.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - {{- end }} - containers: - - name: nginx - image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" - imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} - command: - - nginx - - -c - - /etc/nginx/nginx.conf - - -g - - daemon off; - ports: - - name: http-internal - containerPort: {{ .Values.service.internalPort }} - readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.internalPort }} - initialDelaySeconds: 10 - volumeMounts: - - name: config - subPath: nginx.conf - mountPath: /etc/nginx/nginx.conf - readOnly: true - - name: config - subPath: robots.txt - mountPath: /usr/share/nginx/html/robots.txt - readOnly: true - resources: - {{- toYaml .Values.nginx.resources | nindent 12 }} - - name: sanic - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - python3 -m osf_pigeon - env: - {{- include "osf-pigeon.environment" . | nindent 12 }} - ports: - - name: http-external - containerPort: {{ .Values.service.externalPort }} - readinessProbe: - httpGet: - path: / - port: {{ .Values.service.externalPort }} - volumeMounts: - - name: localcache - mountPath: /tmp/pigeonlocalcache - {{- if .Values.persistence.enabled }} - - name: data - mountPath: {{ .Values.persistence.mountPath }} - {{- end }} - resources: - {{- toYaml .Values.sanic.resources | nindent 12 }} - volumes: - - name: localcache - emptyDir: {} - - name: data - {{- if .Values.persistence.enabled }} - persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim | default (include "osf-pigeon.fullname" .) }} - {{- else }} - emptyDir: {} - {{- end }} - - name: config - configMap: - name: {{ template "osf-pigeon.fullname" . }} - - name: secret - secret: - secretName: {{ template "osf-pigeon.fullname" . }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - {{- range .Values.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - {{- if hasKey .Values.persistence "storageClass" }} - storageClassName: {{ .Values.persistence.storageClass | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.persistence.size | quote }} - {{- end }} diff --git a/osf-pigeon/templates/hpa.yaml b/osf-pigeon/templates/hpa.yaml deleted file mode 100644 index 28b5df92..00000000 --- a/osf-pigeon/templates/hpa.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "osf-pigeon.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} diff --git a/osf-pigeon/templates/ingress.yaml b/osf-pigeon/templates/ingress.yaml deleted file mode 100644 index 4cf9c446..00000000 --- a/osf-pigeon/templates/ingress.yaml +++ /dev/null @@ -1,49 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "osf-pigeon.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- $serviceName := include "osf-pigeon.fullname" . -}} - {{- $servicePort := .Values.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls)) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "osf-pigeon.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- end -}} -{{- end -}} diff --git a/osf-pigeon/templates/main.yaml b/osf-pigeon/templates/main.yaml new file mode 100644 index 00000000..adaf50ef --- /dev/null +++ b/osf-pigeon/templates/main.yaml @@ -0,0 +1,10 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pvc" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.certificate" (dict "root" . "name" "cert" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.service" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.ingress" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.hpa" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pdb" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "" "values" .Values.main) }} diff --git a/osf-pigeon/templates/networkpolicy.yaml b/osf-pigeon/templates/networkpolicy.yaml deleted file mode 100644 index 724e2fe1..00000000 --- a/osf-pigeon/templates/networkpolicy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - podSelector: - matchLabels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - ingress: - - ports: - - port: {{ .Values.service.internalPort }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "osf-pigeon.fullname" . }}-client: "true" - {{- end }} - {{- if .Values.nginx.vts.enabled }} - - ports: - - port: {{ .Values.nginx.vts.internalPort }} - {{- end }} - egress: {{- toYaml .Values.networkPolicy.egress | nindent 4 }} -{{- end }} diff --git a/osf-pigeon/templates/pdb.yaml b/osf-pigeon/templates/pdb.yaml deleted file mode 100644 index 8bbf40fc..00000000 --- a/osf-pigeon/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "osf-pigeon.fullname" . }}" - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/osf-pigeon/templates/secret.yaml b/osf-pigeon/templates/secret.yaml deleted file mode 100644 index 9bd78874..00000000 --- a/osf-pigeon/templates/secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: -{{- range $key, $value := .Values.secretEnvs }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} -{{- range $key, $value := .Values.secretFiles }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} diff --git a/osf-pigeon/templates/service.yaml b/osf-pigeon/templates/service.yaml deleted file mode 100644 index 00bf4965..00000000 --- a/osf-pigeon/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "osf-pigeon.fullname" . }} - labels: - app: {{ template "osf-pigeon.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "osf-pigeon.name" . }} - release: {{ .Release.Name }} diff --git a/osf-pigeon/values.yaml b/osf-pigeon/values.yaml index d03560e9..3597b19a 100644 --- a/osf-pigeon/values.yaml +++ b/osf-pigeon/values.yaml @@ -1,162 +1,373 @@ -# Default values for osf-pigeon. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name -image: - repository: quay.io/centerforopenscience/osf-pigeon - tag: develop - pullPolicy: Always +## =============== MAIN Component =============== +main: + enabled: true -antiAffinity: soft + replicas: 1 -# strategy: -# rollingUpdate: -# maxSurge: 25% -# maxUnavailable: 25% -# type: RollingUpdate + http: + containers: + nginx: + internalPort: 80 + externalPort: 2020 + serviceType: ClusterIP -budget: - minAvailable: 0 - -nginx: - workerCount: 1 +# ------- Configuration follows for containerName: nginx ------- image: - repository: quay.io/centerforopenscience/nginx + repository: nginx tag: latest pullPolicy: Always + + containerName: nginx + + command: + - nginx + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + + env: [] + + envFrom: [] + + probes: + readiness: + httpGet: + path: /healthz + port: "{{ .Values.main.http.containers.nginx.internalPort }}" + initialDelaySeconds: 10 + + ports: + - name: http-internal + containerPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + volumeMounts: + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + - name: config + mountPath: /usr/share/nginx/html/robots.txt + subPath: robots.txt + readOnly: true + + additionalVolumeMounts: [] + resources: {} - # limits: - # cpu: 1 - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - proxySourceRanges: [] - # - 130.211.0.0/22 - # - 35.191.0.0/16 - realIpHeader: X-Real-IP - realIpRecursive: "off" - vts: + + +# ------- Init containers ------- + initContainers: [] + + additionalInitContainers: [] + + +# ------- Additional containers ------- + sanic: + image: + repository: quay.io/centerforopenscience/osf-pigeon + tag: develop + pullPolicy: Always + resources: + limits: + cpu: 1 + memory: 564Mi + requests: + cpu: 10m + memory: 282Mi + ephemeral-storage: 10Gi + volumeMounts: + - name: localcache + mountPath: /tmp/pigeonlocalcache + #### If Enabled persistence < ---------- + # - name: data + # mountPath: /var/lib/pigeonlocaldata/ + + sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) + + additionalContainers: + - name: sanic + inheritVolumeMountsFrom: sanic # <----- gets volume mounts from sanic set of vars above + image: "{{ .Values.main.sanic.image.repository }}:{{ .Values.main.sanic.image.tag }}" + imagePullPolicy: "{{ .Values.main.sanic.image.pullPolicy }}" + command: + - /bin/sh + - -c + - python3 -m osf_pigeon + env: [] + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "sanic-env") | trim }}' + ports: + - name: http-external + containerPort: "{{ .Values.main.http.containers.nginx.externalPort }}" + protocol: TCP + readinessProbe: + httpGet: + path: / + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + resources: + limits: + cpu: "{{ .Values.main.sanic.resources.limits.cpu }}" + memory: "{{ .Values.main.sanic.resources.limits.memory }}" + requests: + cpu: "{{ .Values.main.sanic.resources.requests.cpu }}" + memory: "{{ .Values.main.sanic.resources.requests.memory }}" + ephemeral-storage: "{{ index .Values.main.sanic.resources.requests \"ephemeral-storage\" }}" + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: localcache + emptyDir: {} + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + additionalVolumes: + - name: data + emptyDir: {} + #### If Enabled persistence < ---------- + # -----> Option 1 to create persistent volume + # + # - name: data # PVC name will be: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + # persistence: + # enabled: true + # accessModes: + # - ReadWriteOnce + # size: 100Gi + # storageClass: "" + # existingClaim: "" + + # -----> Option 2 to create persistent volume + # + # - name: data + # persistentVolumeClaim: + # claimName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + # -----> Option 2 to create persistent volume + # + # PVC name will be '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + # + # persistence: + # enabled: true + # accessModes: + # - ReadWriteOnce + # size: 100Gi + # storageClass: "" + # existingClaim: "" + + +# ------- Affitnity configuration ------- + affinity: {} + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 1 + # podAffinityTerm: + # topologyKey: kubernetes.io/hostname + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: "{{ .Chart.Name }}" + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave component name in main.yaml empty. + + additionalAffinities: [] + + +# ------- Pod Annotations ------- +# Checksum for secret and configmap generated automatically + podAnnotations: [] + + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + service: + enabled: true + type: "{{ .Values.main.http.containers.nginx.serviceType }}" + ports: + - name: http + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + targetPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + ingress: enabled: false - internalPort: 18080 - statusZoneSize: 10m - defaultFilterKey: "$geoip_country_code country::*" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + paths: + - / + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local -sanic: - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -horizontalPodAutoscaler: - enabled: false - maxReplicas: 3 - targetCPUUtilizationPercentage: 90 - -service: - name: http - type: ClusterIP - externalPort: 2020 - internalPort: 80 - -ingress: - enabled: false - # Used to create Ingress record (should used with service.type: ClusterIP). - hosts: - - chart-example.local - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - paths: - - / - tls: - # Secrets must be manually created in the namespace. - # - secretName: osf-io-tls - # hosts: - # - chart-example.local - -certificate: - enabled: false - name: cert - # WORKAROUND: Ingress deploy blocked to GLBC due to race condition w/ missing TLS certificate - # - Issue: https://github.com/jetstack/cert-manager/issues/606 - # - PR: https://github.com/kubernetes/ingress-gce/pull/388 - tls: true - # issuerRef: - # name: letsencrypt-prod - # kind: ClusterIssuer - # commonName: example.org - # dnsNames: - # - example.org - # - subdomain.example.org - # acmeConfig: - # http01: {} - # # ingress: '' - # domains: - # - example.org - # - subdomain.example.org - -networkPolicy: - enabled: false - # Allows external access to the pod, otherwise access is restricted to - # clients with the explicit label. - allowExternal: true - egress: {} - # - to: - # - namespaceSelector: {} - # ports: - # - port: 53 # dns - # protocol: TCP - # - port: 53 # dns - # protocol: UDP - # - to: - # - ipBlock: - # cidr: 0.0.0.0/0 - # except: - # - 10.0.0.0/8 - # - 172.16.0.0/12 - # - 192.168.0.0/16 - # ports: - # - port: 80 # http - # protocol: TCP - # - port: 443 # https - # protocol: TCP - -configEnvs: {} - # DEBUG: "" - -configFiles: {} - # Override configmap files here (and delete the {} above), e.g.: - #robots.txt: |- - # User-agent: * - # Disallow: / - -secretEnvs: {} - # DATACITE_USERNAME: "CHANGEME" - # DATACITE_PASSWORD: "CHANGEME" - # OSF_BEARER_TOKEN: "CHANGEME" - # IA_ACCESS_KEY: "CHANGEME" - # IA_SECRET_KEY: CHANGEME - -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## -persistence: - enabled: false - ## Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessModes: - - ReadWriteOnce - size: 100Gi - - mountPath: /var/lib/pigeonlocaldata/ + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + acmeConfig: + http01: {} + # ingress: '' + domains: + - example.org + - subdomain.example.org + + # additionalCertificates: + # # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name + # - name: example-org-cert + # enabled: false + # # secretName: secret-with-cert + # commonName: example.org + # dnsNames: + # - example.org + # - submdomain.example.org + # issuerRef: + # name: letsencrypt-prod + # kind: ClusterIssuer + # acmeConfig: + # http01: {} + # # ingress: '' + # domains: + # - example.org + # - subdomain.example.org + + +# ------- HPA configuration ------- + hpa: + enabled: false + minReplicas: "{{ .Values.main.replicas }}" + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + + +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 1 + + +# ------- Network Policy configuration ------- +# Network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + networkPolicy: + enabled: false + componentScoped: true # this network policy will be applied to all components + ingressRules: + - ports: + - port: "{{ .Values.main.http.containers.nginx.internalPort }}" + from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "client") | trim }}': "true" + - ports: + - port: "{{ .Values.main.nginx.vts.internalPort }}" + egressRules: + - {} + +# Additional network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalNetworkPolicies: + - name: cert-solver + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- ConfigMap configuration ------- +# ConfigMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + configMap: + enabled: true + tpl: true + data: + robots.txt: | + {{ .Files.Get "files/robots.txt" }} + nginx.conf: | + {{ tpl (.Files.Get "files/nginx.conf") (dict "Values" .Values "root" .) }} + +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalConfigMaps: + - name: common-env + enabled: true + tpl: false + data: {} + - name: sanic-env + enabled: true + tpl: false + data: {} + #### If Enabled persistence < ---------- + # PIGEON_FILESTORE_DIR: /var/lib/pigeonlocaldata/ + # Provide other envs for sanic + + +# ------- Secrets configuration ------- +# Secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: true + includeTls: false + data: {} + +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalSecrets: + - name: common-env + enabled: true + includeTls: false + data: {} + + +# ------- Selectors and etc. ------- + nodeSelector: {} + + +# ------- Other variables which are used accross files or values.yaml ------- + nginx: + workerCount: 1 + proxySourceRanges: [] + realIpHeader: X-Real-IP + realIpRecursive: "off" + vts: + enabled: false + internalPort: 18080 + statusZoneSize: 10m + defaultFilterKey: "$geoip_country_code country::*" From 0870ba94b9fb784be0dc6c11afe0e1a322d2ca47 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Tue, 23 Dec 2025 14:16:52 +0200 Subject: [PATCH 08/11] Fixes to the lib and migrate mfr chart to use the lib --- angular-osf/values.yaml | 5 +- cos-common/README.md | 1 + cos-common/templates/_deployment.tpl | 12 +- cos-common/templates/_helpers.tpl | 64 +- cos-common/values.schema.json | 8 + mfr/Chart.yaml | 24 +- mfr/files/nginx.conf | 117 +++ mfr/requirements.lock | 9 - mfr/requirements.yaml | 9 - mfr/templates/NOTES.txt | 38 +- mfr/templates/_helpers.tpl | 78 -- mfr/templates/autoroll-cron.yaml | 36 +- mfr/templates/certificate-networkpolicy.yaml | 20 - mfr/templates/certificate.yaml | 34 - mfr/templates/configmap.yaml | 137 ---- mfr/templates/deployment.yaml | 171 ---- mfr/templates/hpa.yaml | 31 - mfr/templates/ingress.yaml | 68 -- mfr/templates/main.yaml | 10 + mfr/templates/networkpolicy.yaml | 30 - mfr/templates/pdb.yaml | 21 - mfr/templates/secret.yaml | 17 - mfr/templates/service.yaml | 19 - mfr/templates/worker-deployment.yaml | 101 --- mfr/templates/worker-hpa.yaml | 20 - mfr/templates/worker-pdb.yaml | 23 - mfr/templates/worker.yaml | 8 + mfr/values.yaml | 797 +++++++++++++------ osf-cas/values.yaml | 12 +- osf-graveyvalet/values.yaml | 60 +- osf-pigeon/values.yaml | 29 +- 31 files changed, 842 insertions(+), 1167 deletions(-) create mode 100644 mfr/files/nginx.conf delete mode 100644 mfr/requirements.lock delete mode 100644 mfr/requirements.yaml delete mode 100644 mfr/templates/_helpers.tpl delete mode 100644 mfr/templates/certificate-networkpolicy.yaml delete mode 100644 mfr/templates/certificate.yaml delete mode 100644 mfr/templates/configmap.yaml delete mode 100644 mfr/templates/deployment.yaml delete mode 100644 mfr/templates/hpa.yaml delete mode 100644 mfr/templates/ingress.yaml create mode 100644 mfr/templates/main.yaml delete mode 100644 mfr/templates/networkpolicy.yaml delete mode 100644 mfr/templates/pdb.yaml delete mode 100644 mfr/templates/secret.yaml delete mode 100644 mfr/templates/service.yaml delete mode 100644 mfr/templates/worker-deployment.yaml delete mode 100644 mfr/templates/worker-hpa.yaml delete mode 100644 mfr/templates/worker-pdb.yaml create mode 100644 mfr/templates/worker.yaml diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index 55937f28..5e40c305 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -158,8 +158,9 @@ main: # ------- Pod Annotations ------- -# Checksum for configmap and secret will be added automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.main "resource" "configmap") }}' # ------- Service configuration ------- diff --git a/cos-common/README.md b/cos-common/README.md index 4bb21b83..8e7a4f34 100644 --- a/cos-common/README.md +++ b/cos-common/README.md @@ -143,6 +143,7 @@ app: - **NetworkPolicy**: defaults to namespace-local ingress allow; egress only if `allowEgress: true` or `extraEgressRules` present. Use `componentScoped: false` to drop the component label when you want one policy to cover multiple components. `additionalNetworkPolicies[]` supported. - **Certificates**: renders cert-manager `Certificate`; `issuerRef` required when enabled. `certificate.acmeConfig` maps to `spec.acme.config[]` (defaults `http01.ingress` to the chart fullname when not set). `additionalCertificates[]` available. - **Persistence**: component-level `persistence` or `volumes[].persistence` can auto-create PVCs (unless `existingClaim`); per-volume persistence forbids `emptyDir` and wires the volume to the claim automatically. +- **Additional containers**: `sidecars`/`additionalContainers` can inherit mounts or resources from another container in the same component via `inheritVolumeMountsFrom` / `inheritResourcesFrom` (explicit fields on the child override inherited ones). - **CronJob**: `schedule` is required; job-spec knobs (`parallelism`, `backoffLimit`, `podFailurePolicy`, etc.) live directly under the component block. - **Annotations**: `annotations` apply broadly by default; set `annotationsWorkloadOnly: true` or use `workloadAnnotations` to scope/override annotations on the workload resources only (deployment/statefulset/job/cronjob), e.g., for Helm hooks. - **StatefulSet**: define `serviceName` and `volumeClaimTemplates` as needed; supports `persistentVolumeClaimRetentionPolicy`. diff --git a/cos-common/templates/_deployment.tpl b/cos-common/templates/_deployment.tpl index 961004b5..115bea9e 100644 --- a/cos-common/templates/_deployment.tpl +++ b/cos-common/templates/_deployment.tpl @@ -35,16 +35,8 @@ spec: {{ end }} template: metadata: - labels: - {{- include "cos-common.podLabels" . | nindent 8 }} - {{- with $vals.podLabels }} - {{- tpl (toYaml .) $.root | nindent 8 }} - {{- end }} - annotations: - {{- with $vals.podAnnotations }} - {{- tpl (toYaml .) $.root | nindent 8 }} - {{- end }} - {{- include "cos-common.podChecksums" . | nindent 8 }} + {{- /* Pod labels/annotations stay aligned with selectors. */}} + {{- include "cos-common.podMetadata" . | nindent 6 }} spec: {{- /* Shared pod spec helper wires containers, volumes, TLS, affinities, etc. */}} {{- include "cos-common.podSpec" . | nindent 6 }} diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl index 63cece88..120bafe4 100644 --- a/cos-common/templates/_helpers.tpl +++ b/cos-common/templates/_helpers.tpl @@ -235,36 +235,6 @@ workload-specific additions via `workloadAnnotations`. {{- end }} {{- end }} -{{/* -Compute pod checksum annotations for resources that affect runtime. -Currently: -- ConfigMap -- Secret -*/}} -{{- define "cos-common.podChecksums" -}} - -{{- /* ConfigMap checksum */ -}} -{{- if and .values.configMap (default false .values.configMap.enabled) }} -checksum/configmap: {{ include "cos-common.componentChecksum" (dict - "root" .root - "name" .name - "values" .values - "resource" "configmap" -) }} -{{- end }} - -{{- /* Secret checksum */ -}} -{{- if and .values.secret (default false .values.secret.enabled) }} -checksum/secret: {{ include "cos-common.componentChecksum" (dict - "root" .root - "name" .name - "values" .values - "resource" "secret" -) }} -{{- end }} - -{{- end }} - {{/* Render pod metadata labels and annotations. */}} @@ -272,14 +242,19 @@ Render pod metadata labels and annotations. labels: {{- include "cos-common.podLabels" . | nindent 2 }} {{- with .values.podLabels }} - {{ tpl (toYaml .) .root | nindent 2 }} - {{ end }} + {{- range $k, $v := . }} + {{ $k }}: {{ tpl (printf "%s" $v) $.root | quote }} + {{- end }} + {{- end }} + {{- with .values.podAnnotations }} annotations: - {{- $podAnns := tpl (toYaml .) .root | trimSuffix "\n" }} - {{ $podAnns | nindent 2 }} -{{ end }} -{{ end }} + {{- range $k, $v := . }} + {{ $k }}: {{ tpl (printf "%s" $v) $.root | quote }} + {{- end }} +{{- end }} +{{- end }} + {{/* Helper to render list values with tpl evaluation. @@ -462,7 +437,7 @@ resources: {{/* Render additional containers (sidecars/extra). -Merges sidecars and additionalContainers, lets you inherit mounts from another container, +Merges sidecars and additionalContainers, lets you inherit mounts/resources from another container, adds TLS mounts, normalizes ports/probes, and strips helper-only keys before render. */}} {{- define "cos-common.additionalContainers" -}} @@ -480,6 +455,7 @@ adds TLS mounts, normalizes ports/probes, and strips helper-only keys before ren {{- $c := deepCopy . -}} {{- $name := default "" $c.name -}} {{- $volumeMounts := list -}} + {{- $inheritedResources := dict -}} {{- if $c.inheritVolumeMountsFrom }} {{- /* copy mounts from another named container section (e.g., main.daphne) */ -}} {{- $inheritFrom := tpl (toString $c.inheritVolumeMountsFrom) $.root -}} @@ -493,8 +469,18 @@ adds TLS mounts, normalizes ports/probes, and strips helper-only keys before ren {{- end }} {{- end }} {{- end }} + {{- if $c.inheritResourcesFrom }} + {{- $inheritResourcesFrom := tpl (toString $c.inheritResourcesFrom) $.root -}} + {{- $src := index $vals $inheritResourcesFrom -}} + {{- if kindIs "map" $src }} + {{- with $src.resources }} + {{- $inheritedResources = merge (dict) $inheritedResources . -}} + {{- end }} + {{- end }} + {{- end }} {{- /* remove helper-only key so it doesn't reach k8s */ -}} {{- $_ := unset $c "inheritVolumeMountsFrom" -}} + {{- $_ := unset $c "inheritResourcesFrom" -}} {{- $volumeMounts = concat $volumeMounts (default (list) $c.volumeMounts) -}} {{- if $tlsConfigs }} {{- range $app, $cfg := $tlsConfigs }} @@ -506,6 +492,10 @@ adds TLS mounts, normalizes ports/probes, and strips helper-only keys before ren {{- if gt (len $volumeMounts) 0 }} {{- $_ := set $c "volumeMounts" $volumeMounts -}} {{- end }} + {{- $resources := merge (dict) $inheritedResources (default (dict) $c.resources) -}} + {{- if gt (len $resources) 0 }} + {{- $_ := set $c "resources" $resources -}} + {{- end }} {{- if $c.ports }} {{- $ports := list }} {{- range $c.ports }} diff --git a/cos-common/values.schema.json b/cos-common/values.schema.json index 6d7b1144..ce27b829 100644 --- a/cos-common/values.schema.json +++ b/cos-common/values.schema.json @@ -262,6 +262,10 @@ "type": "object", "additionalProperties": true }, + "inheritResourcesFrom": { + "type": "string", + "description": "Name of another container entry in this component whose resources should be merged into this container." + }, "securityContext": { "type": "object", "additionalProperties": true @@ -272,6 +276,10 @@ "$ref": "#/definitions/volumeMount" } }, + "inheritVolumeMountsFrom": { + "type": "string", + "description": "Name of another container entry in this component whose volumeMounts should be copied into this container." + }, "livenessProbe": { "$ref": "#/definitions/probe" }, diff --git a/mfr/Chart.yaml b/mfr/Chart.yaml index d515e14a..dc725f12 100644 --- a/mfr/Chart.yaml +++ b/mfr/Chart.yaml @@ -1,7 +1,8 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes +apiVersion: v2 name: mfr -version: 0.9.3 +description: Modular File Renderer +type: application +version: 1.0.0 keywords: - renderer sources: @@ -10,5 +11,18 @@ maintainers: - name: Matt Frazier email: matt@cos.io url: https://github.com/mfraezz -engine: gotpl -tillerVersion: '>=2.7.0' +dependencies: + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" + - name: maintenance + version: 0.2.0 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: maintenance.enabled, global.maintenance.enabled + - name: rabbitmq + version: 6.9.1 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: rabbitmq.enabled, global.rabbitmq.enabled diff --git a/mfr/files/nginx.conf b/mfr/files/nginx.conf new file mode 100644 index 00000000..9dd5f27d --- /dev/null +++ b/mfr/files/nginx.conf @@ -0,0 +1,117 @@ +user nginx; +worker_processes {{ .Values.nginx.workerCount }}; + +load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; +{{- if .Values.nginx.vts.enabled }} +load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; +load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; +{{- end }} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + access_log /var/log/nginx/access.log main; + + real_ip_header {{ .Values.nginx.realIpHeader }}; + real_ip_recursive {{ .Values.nginx.realIpRecursive }}; + {{- range .Values.nginx.proxySourceRanges }} + set_real_ip_from {{ . }}; + {{- end }} + + {{- if .Values.nginx.vts.enabled }} + geoip_country /etc/nginx/GeoIP.dat; + geoip_city /etc/nginx/GeoLiteCity.dat; + geoip_proxy_recursive on; + {{- range .Values.nginx.proxySourceRanges }} + geoip_proxy {{ . }}; + {{- end }} + + vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; + vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; + {{- end }} + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 620s; + keepalive_requests 10000; + types_hash_max_size 2048; + server_tokens off; + + gzip on; + gzip_proxied any; + gzip_disable "msie6"; + gzip_min_length 1400; + gzip_vary on; + gzip_buffers 4 32k; + gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + brotli on; + brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + {{- if .Values.nginx.vts.enabled }} + server { + listen {{ .Values.nginx.vts.internalPort }}; + server_name _; + + location /healthz { + access_log off; + return 200; + } + + location /nginx_status { + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + } + {{- end }} + + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + keepalive_timeout 620s; + client_max_body_size 25M; + server_name _; + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /healthz { + access_log off; + return 200; + } + + location = /robots.txt { + alias /usr/share/nginx/html/robots.txt; + } + + location / { + # Disable caching of application requests + add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; + add_header Expires "-1"; + add_header Pragma "no-cache"; + + # Mitigate HTTPoxy Vulnerability + # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ + proxy_set_header Proxy ""; + + proxy_buffering off; + proxy_request_buffering off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://127.0.0.1:{{ .Values.main.http.containers.nginx.externalPort }}; + } + } +} diff --git a/mfr/requirements.lock b/mfr/requirements.lock deleted file mode 100644 index b75be2e4..00000000 --- a/mfr/requirements.lock +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: maintenance - repository: https://centerforopenscience.github.io/helm-charts/ - version: 0.2.0 -- name: rabbitmq - repository: https://centerforopenscience.github.io/helm-charts/ - version: 6.9.1 -digest: sha256:643780ebc32233b9794515bc03f8e99bfb2dca7797a3003dfd29d23a8a9e61b2 -generated: "2025-08-20T15:54:00.287611-04:00" diff --git a/mfr/requirements.yaml b/mfr/requirements.yaml deleted file mode 100644 index 1da5f529..00000000 --- a/mfr/requirements.yaml +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: - - name: maintenance - version: 0.2.0 - repository: https://centerforopenscience.github.io/helm-charts/ - condition: maintenance.enabled, global.maintenance.enabled - - name: rabbitmq - repository: https://centerforopenscience.github.io/helm-charts/ - version: 6.9.1 - condition: rabbitmq.enabled, global.rabbitmq.enabled diff --git a/mfr/templates/NOTES.txt b/mfr/templates/NOTES.txt index 026c4b68..17a43b24 100644 --- a/mfr/templates/NOTES.txt +++ b/mfr/templates/NOTES.txt @@ -1,17 +1,23 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "mfr.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "mfr.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "mfr.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "mfr.fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +{{- $fullname := include "cos-common.fullname" (dict "root" . "name" "") | trim -}} +{{- $service := .Values.main.service -}} +{{- $externalPort := .Values.main.http.containers.nginx.externalPort -}} +{{- $internalPort := .Values.main.http.containers.nginx.internalPort -}} + +1. Main service: {{ $fullname }} +{{- if and $service (eq $service.enabled true) }} +{{- if contains "LoadBalancer" $service.type }} + Wait for the external IP, then: + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ $fullname }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ $externalPort }} +{{- else if contains "NodePort" $service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ $fullname }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "cos-common.chartName" (dict "root" .) }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:{{ $internalPort }} +{{- end }} +{{- else }} + Service disabled. {{- end }} diff --git a/mfr/templates/_helpers.tpl b/mfr/templates/_helpers.tpl deleted file mode 100644 index 32c1c2fa..00000000 --- a/mfr/templates/_helpers.tpl +++ /dev/null @@ -1,78 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "mfr.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "mfr.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified migration name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "mfr.autoroll.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.autoroll.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "mfr.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified worker name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "mfr.worker.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.worker.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified maintenance name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "maintenance.fullname" -}} -{{- $name := "maintenance" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Overridable deployment annotations -*/}} -{{- define "mfr.deploymentAnnotations" -}} -checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} -{{- end -}} - -{{- define "mfr.environment" -}} -{{- $fullname := include "mfr.fullname" . -}} -{{- range $key, $value := .Values.configEnvs }} -- name: {{ $key }} - valueFrom: - configMapKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- range $key, $value := .Values.secretEnvs }} -- name: {{ $key }} - valueFrom: - secretKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- end -}} diff --git a/mfr/templates/autoroll-cron.yaml b/mfr/templates/autoroll-cron.yaml index 81ed3a7e..75ad4eb1 100644 --- a/mfr/templates/autoroll-cron.yaml +++ b/mfr/templates/autoroll-cron.yaml @@ -1,9 +1,12 @@ {{- if .Values.autoroll.enabled -}} +{{- $base := include "cos-common.fullname" (dict "root" . "name" .Values.autoroll.name) | trim -}} +{{- $serviceAccount := printf "%s-deleter" $base -}} +{{- $role := printf "%s-role" $base -}} +{{- $roleBinding := printf "%s-rolebinding" $base -}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: role-pod-list-delete - namespace: {{ .Release.Namespace }} + name: {{ $role }} rules: - apiGroups: [""] resources: ["pods"] @@ -12,33 +15,28 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: deleter-rolebinding - namespace: {{ .Release.Namespace }} + name: {{ $roleBinding }} subjects: - kind: ServiceAccount - name: deleter + name: {{ $serviceAccount }} namespace: {{ .Release.Namespace }} roleRef: kind: ClusterRole - name: role-pod-list-delete + name: {{ $role }} apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ServiceAccount metadata: - name: deleter + name: {{ $serviceAccount }} namespace: {{ .Release.Namespace }} --- apiVersion: batch/v1 kind: CronJob metadata: - name: {{ template "mfr.autoroll.fullname" . }} + name: {{ $base }} labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.autoroll.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} + {{- include "cos-common.labels" (dict "root" . "name" .Values.autoroll.name "values" dict) | nindent 4 }} spec: schedule: {{ default "0 4 * * *" .Values.autoroll.schedule | quote }} startingDeadlineSeconds: {{ default 900 .Values.autoroll.startingDeadlineSeconds }} @@ -48,24 +46,22 @@ spec: activeDeadlineSeconds: {{ default 14400 .Values.autoroll.activeDeadlineSeconds }} template: metadata: - name: "{{ .Release.Name }}" labels: - app: {{ template "mfr.name" . }} - component: "{{ .Values.autoroll.name }}" - release: {{ .Release.Name }} + {{- include "cos-common.podLabels" (dict "root" . "name" .Values.autoroll.name) | nindent 12 }} spec: - serviceAccountName: deleter + serviceAccountName: {{ $serviceAccount }} restartPolicy: Never containers: - name: {{ .Values.autoroll.name }} image: {{ default "bitnami/kubectl:latest" .Values.autoroll.image | quote }} - imagePullPolicy: {{ .Values.image.pullPolicy }} + imagePullPolicy: {{ .Values.appImage.pullPolicy }} command: - kubectl args: - delete - pod - - -l app=mfr,component=mfr + - -l + - app.kubernetes.io/name={{ include "cos-common.chartName" (dict "root" .) }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component={{ include "cos-common.componentName" (dict "root" . "name" "") }} nodeSelector: {{- toYaml .Values.autoroll.nodeSelector | nindent 12 }} {{- end -}} diff --git a/mfr/templates/certificate-networkpolicy.yaml b/mfr/templates/certificate-networkpolicy.yaml deleted file mode 100644 index 63c2aa2d..00000000 --- a/mfr/templates/certificate-networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if (and .Values.networkPolicy.enabled .Values.certificate.enabled) }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: "{{ template "mfr.certificate.fullname" . }}" - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - policyTypes: - - Ingress - podSelector: - matchExpressions: - - {key: acme.cert-manager.io/http01-solver, operator: Exists} - ingress: - - from: [] -{{- end }} diff --git a/mfr/templates/certificate.yaml b/mfr/templates/certificate.yaml deleted file mode 100644 index 434a6704..00000000 --- a/mfr/templates/certificate.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if and .Values.certificate.enabled .Values.certificate.createCert -}} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "{{ template "mfr.certificate.fullname" . }}" - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - secretName: "{{ template "mfr.certificate.fullname" . }}" - issuerRef: - name: {{ .Values.certificate.issuerRef.name }} - kind: {{ .Values.certificate.issuerRef.kind }} - commonName: {{ .Values.certificate.commonName }} - dnsNames: - {{- range .Values.certificate.dnsNames }} - - {{ . }} - {{- end }} - acme: - config: - - http01: - {{- if hasKey .Values.certificate.acmeConfig.http01 "ingress" }} - ingress: {{ .Values.certificate.acmeConfig.http01.ingress }} - {{- else }} - ingress: {{ template "mfr.fullname" . }} - {{- end }} - domains: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} -{{- end -}} diff --git a/mfr/templates/configmap.yaml b/mfr/templates/configmap.yaml deleted file mode 100644 index d1881f4b..00000000 --- a/mfr/templates/configmap.yaml +++ /dev/null @@ -1,137 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: -{{- define "mfr.inlineconfigs" }} -nginx.conf: |- - user nginx; - worker_processes {{ .Values.nginx.workerCount }}; - - load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; - {{- if .Values.nginx.vts.enabled }} - load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; - load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; - {{- end }} - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - access_log /var/log/nginx/access.log main; - - real_ip_header {{ .Values.nginx.realIpHeader }}; - real_ip_recursive {{ .Values.nginx.realIpRecursive }}; - {{- range .Values.nginx.proxySourceRanges }} - set_real_ip_from {{ . }}; - {{- end }} - - {{- if .Values.nginx.vts.enabled }} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - {{- range .Values.nginx.proxySourceRanges }} - geoip_proxy {{ . }}; - {{- end }} - - vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; - {{- end }} - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 620s; - keepalive_requests 10000; - types_hash_max_size 2048; - server_tokens off; - - gzip on; - gzip_proxied any; - gzip_disable "msie6"; - gzip_min_length 1400; - gzip_vary on; - gzip_buffers 4 32k; - gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - brotli on; - brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - {{- if .Values.nginx.vts.enabled }} - server { - listen {{ .Values.nginx.vts.internalPort }}; - server_name _; - - location /healthz { - access_log off; - return 200; - } - - location /nginx_status { - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - } - } - {{- end }} - - server { - listen {{ .Values.service.internalPort }}; - keepalive_timeout 620s; - client_max_body_size 25M; - server_name _; - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /healthz { - access_log off; - return 200; - } - - location = /robots.txt { - alias /usr/share/nginx/html/robots.txt; - } - - location / { - # Disable caching of application requests - add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; - add_header Expires "-1"; - add_header Pragma "no-cache"; - - # Mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - proxy_buffering off; - proxy_request_buffering off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://127.0.0.1:{{ .Values.service.externalPort }}; - } - } - } -{{- end -}} - {{- range $key, $value := .Values.configEnvs }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- range $key, $value := merge .Values.configFiles (include "mfr.inlineconfigs" . | fromYaml) ((.Files.Glob "files/*").AsConfig | fromYaml) }} - {{ $key }}: |- - {{- $value | nindent 4 }} - {{- end }} diff --git a/mfr/templates/deployment.yaml b/mfr/templates/deployment.yaml deleted file mode 100644 index 05bc1388..00000000 --- a/mfr/templates/deployment.yaml +++ /dev/null @@ -1,171 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - replicas: {{ .Values.replicaCount }} - {{- if .Values.strategy }} - strategy: - {{- toYaml .Values.strategy | nindent 4 }} - {{- end }} - template: - metadata: - labels: - app: {{ template "mfr.name" . }} - component: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - annotations: - {{- include "mfr.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "mfr.name" . }} - component: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "mfr.name" . }} - component: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - {{- end }} - initContainers: - - name: exportdir - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - mkdir /tmp/mfrlocalcache/export - chmod 777 /tmp/mfrlocalcache/export - volumeMounts: - - mountPath: /tmp/mfrlocalcache - name: localcache - containers: - - name: nginx - image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" - imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} - command: - - nginx - - -c - - /etc/nginx/nginx.conf - - -g - - daemon off; - ports: - - name: http-internal - containerPort: {{ .Values.service.internalPort }} - readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.internalPort }} - initialDelaySeconds: 10 - volumeMounts: - - name: config - subPath: nginx.conf - mountPath: /etc/nginx/nginx.conf - readOnly: true - - name: config - subPath: robots.txt - mountPath: /usr/share/nginx/html/robots.txt - readOnly: true - resources: - {{- toYaml .Values.nginx.resources | nindent 12 }} - - name: tornado - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - gosu - - www-data - - invoke - - server - env: - {{- include "mfr.environment" . | nindent 12 }} - - name: ENV - value: kube - ports: - - name: http-external - containerPort: {{ .Values.service.externalPort }} - readinessProbe: - httpGet: - path: /status - port: {{ .Values.service.externalPort }} - volumeMounts: - - name: secret - subPath: settings.json - mountPath: /home/.cos/mfr-kube.json - readOnly: true - - name: localcache - mountPath: /tmp/mfrlocalcache - resources: - {{- toYaml .Values.tornado.resources | nindent 12 }} - - name: unoserver - image: "{{ .Values.unoserver.image.repository }}:{{ .Values.unoserver.image.tag }}" - imagePullPolicy: {{ .Values.unoserver.image.pullPolicy }} - command: - - /usr/bin/python3.12 - - /usr/bin/unoserver - - --interface - - '0.0.0.0' - - --port - - '2003' - - --uno-interface - - '127.0.0.1' - - --uno-port - - '2002' - - --verbose - livenessProbe: - exec: - command: - - /bin/sh - - -c - - |- - cd /tmp - touch test.txt - timeout 10 /usr/bin/python3.12 /usr/bin/unoconvert --convert-to html test.txt - - exit $? - initialDelaySeconds: 30 - periodSeconds: 10 - ports: - - name: unoserver - containerPort: 2003 - volumeMounts: - - name: localcache - mountPath: /tmp/mfrlocalcache - resources: - {{- toYaml .Values.unoserver.resources | nindent 12 }} - volumes: - - name: localcache - emptyDir: {} - - name: config - configMap: - name: {{ template "mfr.fullname" . }} - - name: secret - secret: - secretName: {{ template "mfr.fullname" . }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} diff --git a/mfr/templates/hpa.yaml b/mfr/templates/hpa.yaml deleted file mode 100644 index 158fc13a..00000000 --- a/mfr/templates/hpa.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "mfr.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - {{- if .Values.horizontalPodAutoscaler.metrics }} - metrics: - {{ range $name, $target := .Values.horizontalPodAutoscaler.metrics }} - - type: Resource - resource: - name: {{ $name }} - target: - type: Utilization - averageUtilization: {{ $target }} - {{- end }} - {{- else if .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage -}} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} - {{- end -}} -{{- end -}} diff --git a/mfr/templates/ingress.yaml b/mfr/templates/ingress.yaml deleted file mode 100644 index 1a3731bc..00000000 --- a/mfr/templates/ingress.yaml +++ /dev/null @@ -1,68 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "mfr.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- if .Values.maintenance.enabled }} - {{- $serviceName := include "maintenance.fullname" . -}} - {{- $servicePort := .Values.maintenance.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- else -}} - {{- $serviceName := include "mfr.fullname" . -}} - {{- $servicePort := .Values.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls)) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "mfr.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- end -}} -{{- end -}} diff --git a/mfr/templates/main.yaml b/mfr/templates/main.yaml new file mode 100644 index 00000000..544451fb --- /dev/null +++ b/mfr/templates/main.yaml @@ -0,0 +1,10 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.service" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.ingress" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.hpa" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pdb" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pvc" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.certificate" (dict "root" . "name" "cert" "values" .Values.main) }} diff --git a/mfr/templates/networkpolicy.yaml b/mfr/templates/networkpolicy.yaml deleted file mode 100644 index 14ad32f6..00000000 --- a/mfr/templates/networkpolicy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - podSelector: - matchLabels: - app: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - ingress: - - ports: - - port: {{ .Values.service.internalPort }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "mfr.fullname" . }}-client: "true" - {{- end }} - {{- if .Values.nginx.vts.enabled }} - - ports: - - port: {{ .Values.nginx.vts.internalPort }} - {{- end }} - egress: {{- toYaml .Values.networkPolicy.egress | nindent 4 }} -{{- end }} diff --git a/mfr/templates/pdb.yaml b/mfr/templates/pdb.yaml deleted file mode 100644 index 18c665af..00000000 --- a/mfr/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "mfr.fullname" . }}" - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "mfr.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/mfr/templates/secret.yaml b/mfr/templates/secret.yaml deleted file mode 100644 index 6b339382..00000000 --- a/mfr/templates/secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: -{{- range $key, $value := .Values.secretEnvs }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} -{{- range $key, $value := .Values.secretFiles }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} diff --git a/mfr/templates/service.yaml b/mfr/templates/service.yaml deleted file mode 100644 index 33216f19..00000000 --- a/mfr/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "mfr.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "mfr.name" . }} - release: {{ .Release.Name }} diff --git a/mfr/templates/worker-deployment.yaml b/mfr/templates/worker-deployment.yaml deleted file mode 100644 index 6116e68a..00000000 --- a/mfr/templates/worker-deployment.yaml +++ /dev/null @@ -1,101 +0,0 @@ -{{- if .Values.worker.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "mfr.worker.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.worker.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "mfr.name" . }} - component: "{{ .Values.worker.name }}" - release: {{ .Release.Name }} - replicas: {{ .Values.worker.replicaCount }} - template: - metadata: - labels: - app: {{ template "mfr.name" . }} - component: "{{ .Values.worker.name }}" - release: {{ .Release.Name }} - annotations: - {{- include "mfr.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.worker.additionalAffinities }} - {{- toYaml .Values.worker.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.worker.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "mfr.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.worker.name }}" - {{- else if eq .Values.worker.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "mfr.name" . }} - release: "{{ .Release.Name }}" - component: "{{ .Values.worker.name }}" - {{- end }} - containers: - - name: {{ .Values.worker.name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - gosu www-data celery --app mfr.tasks.app worker \ - --concurrency "{{ .Values.worker.concurrency }}" --loglevel "{{ .Values.worker.logLevel }}" \ - --hostname $POD_NAME --without-gossip -Ofair - {{- if .Values.worker.maxTasksPerChild }} --max-tasks-per-child "{{ .Values.worker.maxTasksPerChild }}"{{- end }} - {{- if .Values.worker.queues }} --queues "{{ .Values.worker.queues }}"{{- end }} - env: - - name: ENV - value: kube - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- include "mfr.environment" . | nindent 12 }} - {{- range $key, $value := .Values.worker.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - volumeMounts: - - name: secret - subPath: settings.json - mountPath: /home/.cos/mfr-kube.json - readOnly: true - {{- if .Values.worker.volumeMounts }} - {{- toYaml .Values.worker.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.worker.resources }} - resources: - {{- toYaml .Values.worker.resources | nindent 12 }} - {{- end }} - volumes: - - name: secret - secret: - secretName: {{ template "mfr.fullname" . }} - {{- if .Values.worker.volumeMounts }} - {{- toYaml .Values.worker.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.worker.nodeSelector }} - nodeSelector: - {{- toYaml .Values.worker.nodeSelector | nindent 8 }} - {{- end }} -{{- end -}} diff --git a/mfr/templates/worker-hpa.yaml b/mfr/templates/worker-hpa.yaml deleted file mode 100644 index 38e11433..00000000 --- a/mfr/templates/worker-hpa.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.worker.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "mfr.worker.fullname" . }} - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.worker.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "osf.worker.fullname" . }} - minReplicas: {{ .Values.worker.replicaCount }} - maxReplicas: {{ .Values.worker.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.worker.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} diff --git a/mfr/templates/worker-pdb.yaml b/mfr/templates/worker-pdb.yaml deleted file mode 100644 index e0254fe5..00000000 --- a/mfr/templates/worker-pdb.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.worker.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "mfr.worker.fullname" . }}" - labels: - app: {{ template "mfr.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.worker.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "mfr.name" . }} - component: "{{ .Values.worker.name }}" - release: {{ .Release.Name }} - minAvailable: {{ .Values.worker.budget.minAvailable }} -{{- end -}} diff --git a/mfr/templates/worker.yaml b/mfr/templates/worker.yaml new file mode 100644 index 00000000..45e85289 --- /dev/null +++ b/mfr/templates/worker.yaml @@ -0,0 +1,8 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.secret" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.deployment" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.hpa" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.pdb" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "worker" "values" .Values.worker) }} +{{- include "cos-common.pvc" (dict "root" . "name" "worker" "values" .Values.worker) }} + diff --git a/mfr/values.yaml b/mfr/values.yaml index 0d8dca21..6ad4d03a 100644 --- a/mfr/values.yaml +++ b/mfr/values.yaml @@ -1,40 +1,13 @@ -# Default values for mfr. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 - -image: +### ------- Global or Reusable parts across values.yaml ------- +appImage: repository: quay.io/centerforopenscience/mfr tag: develop pullPolicy: Always -antiAffinity: soft - -# strategy: -# rollingUpdate: -# maxSurge: 25% -# maxUnavailable: 25% -# type: RollingUpdate - -budget: - minAvailable: 0 - nginx: workerCount: 1 - image: - repository: quay.io/centerforopenscience/nginx - tag: latest - pullPolicy: Always resources: {} - # limits: - # cpu: 1 - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi proxySourceRanges: [] - # - 130.211.0.0/22 - # - 35.191.0.0/16 realIpHeader: X-Real-IP realIpRecursive: "off" vts: @@ -43,243 +16,585 @@ nginx: statusZoneSize: 10m defaultFilterKey: "$geoip_country_code country::*" -tornado: - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -unoserver: +autoroll: + enabled: false + name: autoroll + schedule: "0 4 * * *" + startingDeadlineSeconds: 900 + activeDeadlineSeconds: 14400 + nodeSelector: {} + image: bitnami/kubectl:latest + +maintenance: + enabled: false + +rabbitmq: + enabled: true + secretEnvs: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + + +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name + +## =============== MAIN Component =============== +main: + + enabled: true + + replicas: 1 + + http: + containers: + nginx: + internalPort: 80 + externalPort: 7778 + serviceType: ClusterIP + +# ------- Configuration follows for containerName: nginx ------- image: - repository: centerforopenscience/unoserver # TODO: update to quay.io + repository: quay.io/centerforopenscience/nginx tag: latest pullPolicy: Always + + containerName: nginx + + command: + - nginx + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + + env: [] + + envFrom: [] + + probes: + readiness: + httpGet: + path: /healthz + port: "{{ .Values.main.http.containers.nginx.internalPort }}" + initialDelaySeconds: 10 + + ports: + - name: http-internal + containerPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + volumeMounts: + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + - name: config + mountPath: /usr/share/nginx/html/robots.txt + subPath: robots.txt + readOnly: true + resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi -autoroll: - enabled: false - name: autoroll -horizontalPodAutoscaler: - enabled: false - maxReplicas: 3 - # metrics: - # cpu: 50 - # memory: 50 - # # OR - # targetCPUUtilizationPercentage: 90 - # # values.horizontalPodAutoscaler.metrics takes priority - - -service: - name: http - type: ClusterIP - externalPort: 7778 - internalPort: 80 - -ingress: - enabled: false - # Used to create Ingress record (should used with service.type: ClusterIP). - hosts: - - chart-example.local - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - paths: - - / - tls: - # Secrets must be manually created in the namespace. - # - secretName: osf-io-tls - # hosts: - # - chart-example.local - -certificate: - enabled: false - createCert: false - name: cert - # WORKAROUND: Ingress deploy blocked to GLBC due to race condition w/ missing TLS certificate - # - Issue: https://github.com/jetstack/cert-manager/issues/606 - # - PR: https://github.com/kubernetes/ingress-gce/pull/388 - tls: true - # issuerRef: - # name: letsencrypt-prod - # kind: ClusterIssuer - # commonName: example.org - # dnsNames: - # - example.org - # - subdomain.example.org - # acmeConfig: - # http01: {} - # # ingress: '' - # domains: - # - example.org - # - subdomain.example.org - -networkPolicy: - enabled: false - # Allows external access to the pod, otherwise access is restricted to - # clients with the explicit label. - allowExternal: true - egress: {} - # - to: - # - namespaceSelector: {} - # ports: - # - port: 53 # dns - # protocol: TCP - # - port: 53 # dns - # protocol: UDP - # - to: - # - ipBlock: - # cidr: 0.0.0.0/0 - # except: - # - 10.0.0.0/8 - # - 172.16.0.0/12 - # - 192.168.0.0/16 - # ports: - # - port: 80 # http - # protocol: TCP - # - port: 443 # https - # protocol: TCP - -configEnvs: {} - # DEBUG: "" - -configFiles: {} - # Override configmap files here (and delete the {} above), e.g.: - #robots.txt: |- - # User-agent: * - # Disallow: / - -secretEnvs: {} - # AWS_ACCESS_KEY_ID: "abc123" - -secretFiles: - settings.json: |- - { - "SERVER_CONFIG": { - "ADDRESS": "0.0.0.0", - "PORT": 7778, - "DEBUG": false, - "XHEADERS": true, - "CORS_ALLOW_ORIGIN": "https://staging.osf.io", - "MAX_BUFFER_SIZE": 157286400, - "PROVIDER_NAME": "osf", - "ALLOWED_PROVIDER_DOMAINS": "https://staging.osf.io/ https://staging-files.osf.io/", - "CACHE_ENABLED": true, - "CACHE_PROVIDER_NAME": "cloudfiles", - "CACHE_PROVIDER_SETTINGS": { - "container": "mfr_staging" - }, - "CACHE_PROVIDER_CREDENTIALS": { - "region": "", - "username": "", - "token": "", - "temp_key": "" - }, - "ANALYTICS": { - "KEEN": { - "PRIVATE": { - "PROJECT_ID": "", - "WRITE_KEY": "" +# ------- Init containers ------- + initContainers: + - name: exportdir + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/sh + - -c + - |- + mkdir /tmp/mfrlocalcache/export + chmod 777 /tmp/mfrlocalcache/export + volumeMounts: + - mountPath: /tmp/mfrlocalcache + name: localcache + + +# ------- Additional containers ------- + tornado: + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + # ephemeral-storage: 10Gi + volumeMounts: + - name: secret + subPath: settings.json + mountPath: /home/.cos/mfr-kube.json + readOnly: true + - name: localcache + mountPath: /tmp/mfrlocalcache + + unoserver: + image: + repository: centerforopenscience/unoserver + tag: latest + pullPolicy: Always + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + # ephemeral-storage: 10Gi + volumeMounts: + - name: localcache + mountPath: /tmp/mfrlocalcache + + additionalContainers: + - name: tornado + inheritVolumeMountsFrom: tornado # <----- gets volume mounts from tornado set of vars above + inheritResourcesFrom: tornado # <----- gets resources from tornado set of vars above + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - gosu + - www-data + - invoke + - server + env: + - name: ENV + value: kube + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + ports: + - name: http-external + containerPort: "{{ .Values.main.http.containers.nginx.externalPort }}" + readinessProbe: + httpGet: + path: /status + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + + - name: unoserver + inheritVolumeMountsFrom: unoserver # <----- gets volume mounts from unoserver set of vars above + inheritResourcesFrom: unoserver # <----- gets resources from unoserver set of vars above + image: "{{ .Values.main.unoserver.image.repository }}:{{ .Values.main.unoserver.image.tag }}" + imagePullPolicy: "{{ .Values.main.unoserver.image.pullPolicy }}" + command: + - /usr/bin/python3.12 + - /usr/bin/unoserver + - --interface + - '0.0.0.0' + - --port + - '2003' + - --uno-interface + - '127.0.0.1' + - --uno-port + - '2002' + - --verbose + livenessProbe: + exec: + command: + - /bin/sh + - -c + - |- + cd /tmp + touch test.txt + timeout 10 /usr/bin/python3.12 /usr/bin/unoconvert --convert-to html test.txt - + exit $? + initialDelaySeconds: 30 + periodSeconds: 10 + ports: + - name: unoserver + containerPort: 2003 + + sidecars: [] + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: localcache + emptyDir: {} + + additionalVolumes: [] + + +# ------- Affinity configuration ------- + affinity: {} + + additionalAffinities: + - podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app.kubernetes.io/name: "{{ .Chart.Name }}" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave component name in main.yaml empty. + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + service: + enabled: true + type: "{{ .Values.main.http.containers.nginx.serviceType }}" + ports: + - name: http + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + targetPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + # secondary: + # - chart-example-2.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + paths: + - / + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local + + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + acmeConfig: + http01: + ingress: 'example' + domains: + - example.org + + # additionalCertificates: + # # certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + name + # - name: example-org-cert + # enabled: false + # secretName: secret-with-cert # default secret name is certificate name + # commonName: example.org + # dnsNames: + # - example.org + # - submdomain.example.org + # issuerRef: + # name: letsencrypt-prod + # kind: ClusterIssuer + # acmeConfig: + # http01: {} + # # ingress: '' + # domains: + # - example.org + # - subdomain.example.org + + +# ------- HPA configuration ------- + hpa: + enabled: false + minReplicas: "{{ .Values.main.replicas }}" + maxReplicas: 3 + metrics: [] + behavior: {} + + +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 0 + + +# ------- Network Policy configuration ------- + networkPolicy: + enabled: false + componentScoped: false # this network policy will be applied to all components + ingressRules: + - ports: + - port: "{{ .Values.main.http.containers.nginx.internalPort }}" + from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}-client': "true" + - ports: + - port: "{{ .Values.nginx.vts.internalPort }}" + egressRules: + - {} + + additionalNetworkPolicies: + - name: cert-solver + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- ConfigMap configuration ------- +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + configMap: + enabled: true + tpl: true + data: + nginx.conf: | + {{ tpl (.Files.Get "files/nginx.conf") (dict "Values" .Values "root" .) }} + robots.txt: |- + {{ .Files.Get "files/robots.txt" }} + +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalConfigMaps: + - name: common-env + enabled: true + tpl: false + data: {} + + +# ------- Secrets configuration ------- +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: true + data: + settings.json: |- + { + "SERVER_CONFIG": { + "ADDRESS": "0.0.0.0", + "PORT": 7778, + "DEBUG": false, + "XHEADERS": true, + "CORS_ALLOW_ORIGIN": "https://staging.osf.io", + "MAX_BUFFER_SIZE": 157286400, + "PROVIDER_NAME": "osf", + "ALLOWED_PROVIDER_DOMAINS": "https://staging.osf.io/ https://staging-files.osf.io/", + "CACHE_ENABLED": true, + "CACHE_PROVIDER_NAME": "cloudfiles", + "CACHE_PROVIDER_SETTINGS": { + "container": "mfr_staging" }, - "PUBLIC": { - "PROJECT_ID": "", - "WRITE_KEY": "" + "CACHE_PROVIDER_CREDENTIALS": { + "region": "", + "username": "", + "token": "", + "temp_key": "" + }, + "ANALYTICS": { + "KEEN": { + "PRIVATE": { + "PROJECT_ID": "", + "WRITE_KEY": "" + }, + "PUBLIC": { + "PROJECT_ID": "", + "WRITE_KEY": "" + } + } + } + }, + "UNOCONV_EXTENSION_CONFIG": { + "SERVER": "127.0.0.1" + }, + "SENTRY_DSN": "", + "LOGGING": { + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "defaultFormatter": { + "()": "waterbutler.core.logging.MaskFormatter", + "format": "[%(asctime)s][%(levelname)s][%(name)s]: %(message)s", + "pattern": "(?<=cookie=)(.*?)(?=&|$)", + "mask": "***" + } + }, + "handlers": { + "consoleHandler": { + "class": "logging.StreamHandler", + "level": "INFO", + "formatter": "defaultFormatter" + } + }, + "loggers": { + "": { + "handlers": [ + "consoleHandler" + ], + "level": "INFO", + "propagate": false + } + }, + "root": { + "level": "INFO", + "handlers": [ + "consoleHandler" + ] } } } - }, - "UNOCONV_EXTENSION_CONFIG": { - "SERVER": "127.0.0.1" - }, - "SENTRY_DSN": "", - "LOGGING": { - "version": 1, - "disable_existing_loggers": false, - "formatters": { - "defaultFormatter": { - "()": "waterbutler.core.logging.MaskFormatter", - "format": "[%(asctime)s][%(levelname)s][%(name)s]: %(message)s", - "pattern": "(?<=cookie=)(.*?)(?=&|$)", - "mask": "***" - } - }, - "handlers": { - "consoleHandler": { - "class": "logging.StreamHandler", - "level": "INFO", - "formatter": "defaultFormatter" - } - }, - "loggers": { - "": { - "handlers": [ - "consoleHandler" - ], - "level": "INFO", - "propagate": false - } - }, - "root": { - "level": "INFO", - "handlers": [ - "consoleHandler" - ] - } - } - } -maintenance: - enabled: false +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalSecrets: + - name: common-env + enabled: true + includeTls: false + data: {} + + +# ------- Selectors and etc. ------- + nodeSelector: {} + tolerations: [] + + +# =============== WORKER Component =============== worker: enabled: true - name: worker - replicaCount: 1 - antiAffinity: soft + component: worker - budget: - minAvailable: 0 + replicas: 1 + +# ------- Configuration follows for containerName: "{{ .Values.worker.component }}" ------- + image: + repository: "{{ .Values.appImage.repository }}" + tag: "{{ .Values.appImage.tag }}" + pullPolicy: "{{ .Values.appImage.pullPolicy }}" + + containerName: "{{ .Values.worker.component }}" concurrency: 1 logLevel: INFO + maxTasksPerChild: null queues: mfr - # maxTasksPerChild: 5 + + command: + - /bin/sh + - -c + - |- + gosu www-data celery --app mfr.tasks.app worker \ + --concurrency "{{ .Values.worker.concurrency }}" --loglevel "{{ .Values.worker.logLevel }}" \ + --hostname $POD_NAME --without-gossip -Ofair + {{- if .Values.worker.maxTasksPerChild }} --max-tasks-per-child "{{ .Values.worker.maxTasksPerChild }}"{{- end }} + {{- if .Values.worker.queues }} --queues "{{ .Values.worker.queues }}"{{- end }} + + env: + - name: ENV + value: kube + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + + probes: {} + + volumeMounts: + - name: secret + subPath: settings.json + mountPath: /home/.cos/mfr-kube.json + readOnly: true + + additionalVolumeMounts: [] resources: {} - # limits: - # cpu: "1" - # memory: 512Mi - # requests: - # cpu: 100m - # memory: 256Mi - - horizontalPodAutoscaler: + + +# ------- Init containers ------- + initContainers: [] + + additionalInitContainers: [] + + +# ------- Additional containers ------- + additionalContainers: [] + + sidecars: [] + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + +# ------- Affinity configuration ------- + affinity: {} + additionalAffinities: + - podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app.kubernetes.io/name: "{{ .Chart.Name }}" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/component: "{{ .Values.worker.component }}" + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + + +# ------- HPA configuration ------- + hpa: enabled: false + minReplicas: "{{ .Values.worker.replicas }}" maxReplicas: 3 - targetCPUUtilizationPercentage: 90 + metrics: [] + behavior: {} - ## Node labels for component pod assignment - ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ - ## - nodeSelector: {} - env: {} +# ------- PDB configuration ------- + pdb: + enabled: false + minAvailable: 0 + - volumeMounts: [] +# ------- Selectors and etc. ------- + nodeSelector: {} -rabbitmq: - enabled: true - secretEnvs: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest + tolerations: [] diff --git a/osf-cas/values.yaml b/osf-cas/values.yaml index 3d42f594..baa7450b 100644 --- a/osf-cas/values.yaml +++ b/osf-cas/values.yaml @@ -205,6 +205,7 @@ main: additionalContainers: - name: apache + inheritResourcesFrom: apache # <----- gets resources from apache set of vars above image: "{{ .Values.main.apache.image.repository }}:{{ .Values.main.apache.image.tag }}" imagePullPolicy: "{{ .Values.main.apache.image.pullPolicy }}" env: [] @@ -236,15 +237,6 @@ main: periodSeconds: 10 successThreshold: 1 failureThreshold: 10 - resources: - limits: - cpu: "{{ .Values.main.apache.resources.limits.cpu }}" - memory: "{{ .Values.main.apache.resources.limits.memory }}" - requests: - cpu: "{{ .Values.main.apache.resources.requests.cpu }}" - memory: "{{ .Values.main.apache.resources.requests.memory }}" - # ephemeral-storage: "{{ index .Values.main.apache.resources.requests \"ephemeral-storage\" }}" - volumeMounts: [] # ------- Volumes configuration for the pod ------- @@ -288,7 +280,7 @@ main: # ------- Pod Annotations ------- - podAnnotations: [] + podAnnotations: {} # ------- Service configuration ------- diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 14c39fd3..2b474c24 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -146,18 +146,19 @@ main: cpu: 0 memory: 256Mi ephemeral-storage: 10Gi - volumeMounts: - - name: localcache - mountPath: /tmp/gravyvaletlocalcache - #### If Enabled persistence < ---------- - # - name: data - # mountPath: /var/lib/gravyvaletlocaldata/ + volumeMounts: + - name: localcache + mountPath: /tmp/gravyvaletlocalcache + #### If Enabled persistence < ---------- + # - name: data + # mountPath: /var/lib/gravyvaletlocaldata/ sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) additionalContainers: - name: daphne inheritVolumeMountsFrom: daphne # <----- gets volume mounts from daphne set of vars above + inheritResourcesFrom: daphne # <----- gets resources from daphne set of vars above image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" command: @@ -183,14 +184,6 @@ main: httpGet: path: /v1/status/ port: "{{ .Values.main.http.containers.nginx.externalPort }}" - resources: - limits: - cpu: "{{ .Values.main.daphne.resources.limits.cpu }}" - memory: "{{ .Values.main.daphne.resources.limits.memory }}" - requests: - cpu: "{{ .Values.main.daphne.resources.requests.cpu }}" - memory: "{{ .Values.main.daphne.resources.requests.memory }}" - ephemeral-storage: "{{ index .Values.main.daphne.resources.requests \"ephemeral-storage\" }}" # ------- Volumes configuration for the pod ------- @@ -265,8 +258,13 @@ main: # ------- Pod Annotations ------- -# Checksum for configmap and secret will be added automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-daphne-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "daphne-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + # ------- Service configuration ------- # service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' @@ -531,8 +529,14 @@ worker: # ------- Pod Annotations ------- -# Checksum for configmap and secret will be added automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + checksum/worker-config-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.worker "resource" "configmap") }}' + checksum/worker-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.worker "resource" "secret") }}' + checksum/worker-secret-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.worker "resource" "secret") }}' # ------- HPA configuration ------- @@ -716,8 +720,14 @@ beat: # ------- Pod Annotations ------- -# Checksum for configmap and secret will be added automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + checksum/beat-config-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.beat "resource" "configmap") }}' + checksum/beat-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.beat "resource" "secret") }}' + checksum/beat-secret-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.beat "resource" "secret") }}' # ------- ConfigMap configuration ------- @@ -818,8 +828,14 @@ migration: # ------- Pod Annotations ------- -# Checksum for configmap and secret will be added automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + checksum/migration-config-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.migration "resource" "configmap") }}' + checksum/migration-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.migration "resource" "secret") }}' + checksum/migration-secret-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "env" "values" .Values.migration "resource" "secret") }}' # ------- ConfigMap configuration ------- diff --git a/osf-pigeon/values.yaml b/osf-pigeon/values.yaml index 3597b19a..e1b5dab3 100644 --- a/osf-pigeon/values.yaml +++ b/osf-pigeon/values.yaml @@ -80,18 +80,19 @@ main: cpu: 10m memory: 282Mi ephemeral-storage: 10Gi - volumeMounts: - - name: localcache - mountPath: /tmp/pigeonlocalcache - #### If Enabled persistence < ---------- - # - name: data - # mountPath: /var/lib/pigeonlocaldata/ + volumeMounts: + - name: localcache + mountPath: /tmp/pigeonlocalcache + #### If Enabled persistence < ---------- + # - name: data + # mountPath: /var/lib/pigeonlocaldata/ sidecars: [] # same as additionalContainers, but maybe you prefer this name more :) additionalContainers: - name: sanic inheritVolumeMountsFrom: sanic # <----- gets volume mounts from sanic set of vars above + inheritResourcesFrom: sanic # <----- gets resources from sanic set of vars above image: "{{ .Values.main.sanic.image.repository }}:{{ .Values.main.sanic.image.tag }}" imagePullPolicy: "{{ .Values.main.sanic.image.pullPolicy }}" command: @@ -114,14 +115,6 @@ main: httpGet: path: / port: "{{ .Values.main.http.containers.nginx.externalPort }}" - resources: - limits: - cpu: "{{ .Values.main.sanic.resources.limits.cpu }}" - memory: "{{ .Values.main.sanic.resources.limits.memory }}" - requests: - cpu: "{{ .Values.main.sanic.resources.requests.cpu }}" - memory: "{{ .Values.main.sanic.resources.requests.memory }}" - ephemeral-storage: "{{ index .Values.main.sanic.resources.requests \"ephemeral-storage\" }}" # ------- Volumes configuration for the pod ------- @@ -186,8 +179,12 @@ main: # ------- Pod Annotations ------- -# Checksum for secret and configmap generated automatically - podAnnotations: [] + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-sanic-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "sanic-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' # ------- Service configuration ------- From b2f85976885fd5d6e4198c74e78cd7412554306d Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Tue, 30 Dec 2025 18:05:37 +0200 Subject: [PATCH 09/11] Migrate WB chart to use cos-common lib --- cos-common/README.md | 2 +- cos-common/templates/_ingress.tpl | 3 +- cos-common/values.schema.json | 3 + osf-cas/values.yaml | 3 +- wb/Chart.yaml | 24 +- wb/files/nginx.conf | 113 +++ wb/requirements.lock | 9 - wb/requirements.yaml | 9 - wb/templates/NOTES.txt | 38 +- wb/templates/_helpers.tpl | 102 --- wb/templates/certificate-networkpolicy.yaml | 20 - wb/templates/certificate.yaml | 34 - wb/templates/configmap.yaml | 134 ---- wb/templates/deployment.yaml | 245 ------ wb/templates/hpa.yaml | 19 - wb/templates/ingress.yaml | 68 -- wb/templates/main.yaml | 10 + wb/templates/networkpolicy.yaml | 30 - wb/templates/pdb.yaml | 21 - wb/templates/secret.yaml | 17 - wb/templates/service.yaml | 33 - wb/values.yaml | 812 ++++++++++++-------- 22 files changed, 676 insertions(+), 1073 deletions(-) create mode 100644 wb/files/nginx.conf delete mode 100644 wb/requirements.lock delete mode 100644 wb/requirements.yaml delete mode 100644 wb/templates/_helpers.tpl delete mode 100644 wb/templates/certificate-networkpolicy.yaml delete mode 100644 wb/templates/certificate.yaml delete mode 100644 wb/templates/configmap.yaml delete mode 100644 wb/templates/deployment.yaml delete mode 100644 wb/templates/hpa.yaml delete mode 100644 wb/templates/ingress.yaml create mode 100644 wb/templates/main.yaml delete mode 100644 wb/templates/networkpolicy.yaml delete mode 100644 wb/templates/pdb.yaml delete mode 100644 wb/templates/secret.yaml delete mode 100644 wb/templates/service.yaml diff --git a/cos-common/README.md b/cos-common/README.md index 8e7a4f34..3d2b535a 100644 --- a/cos-common/README.md +++ b/cos-common/README.md @@ -136,7 +136,7 @@ app: - **ConfigMap**: `tpl: true` renders `.data` through Helm’s engine. `additionalConfigMaps[]` lets you emit extra ConfigMaps without new templates. - **Secret**: `.data` is auto base64’d. `includeTls: true` can merge TLS files from `.Values.tls.*.files`. `additionalSecrets[]` is supported. - **Ingress**: requires `hosts` or `defaultBackend`. Besides the legacy `hosts[]` block, you can use grouped hosts via `hosts.primary`/`hosts.secondary` with `rules[]` (per-rule enablement and `includeForPrimaryHost`/`includeForSecondaryHost`) to fan out shared path sets across host groups; `servicePort` defaults to `service.ports[0]` when not set on a path/backend. -- **Maintenance**: when `.Values.maintenance.enabled` is true, ingress targets the maintenance service/port (`maintenance.service.externalPort` or `maintenance.servicePort`) instead of the component service. +- **Maintenance**: when `.Values.maintenance.enabled` is true, ingress targets the maintenance service/port (`maintenance.service.externalPort` or `maintenance.servicePort`) instead of the component service; override the maintenance service name with `maintenance.service.name` when needed. - **Affinity**: use `affinity` for your base rules and `additionalAffinities[]` to layer on more affinity snippets; later entries override earlier keys. - **HPA**: when `enabled`, `minReplicas`, `maxReplicas`, and `metrics` are required. - **PDB**: when `enabled`, set either `minAvailable` or `maxUnavailable` (not both). diff --git a/cos-common/templates/_ingress.tpl b/cos-common/templates/_ingress.tpl index a7d58fe0..a30238b8 100644 --- a/cos-common/templates/_ingress.tpl +++ b/cos-common/templates/_ingress.tpl @@ -33,7 +33,8 @@ and backward-compatible across services. {{- /* Resolve service names: - default component service - maintenance service (when enabled) */ -}} -{{- $maintenanceServiceName := include "cos-common.fullname" (dict "root" .root "name" "maintenance" "values" $maintenance) -}} +{{- $maintenanceService := default (dict) $maintenance.service -}} +{{- $maintenanceServiceName := coalesce $maintenanceService.name (include "cos-common.fullname" (dict "root" .root "name" "maintenance" "values" $maintenance)) -}} {{- $defaultServiceName := include "cos-common.fullname" (dict "root" .root "name" .name "values" $vals) -}} {{- /* Select backend service name depending on maintenance mode */ -}} diff --git a/cos-common/values.schema.json b/cos-common/values.schema.json index ce27b829..4b241d4c 100644 --- a/cos-common/values.schema.json +++ b/cos-common/values.schema.json @@ -1723,6 +1723,9 @@ "type": "object", "additionalProperties": true, "properties": { + "name": { + "type": "string" + }, "externalPort": { "$ref": "#/definitions/stringOrNumber" } diff --git a/osf-cas/values.yaml b/osf-cas/values.yaml index baa7450b..b49259c4 100644 --- a/osf-cas/values.yaml +++ b/osf-cas/values.yaml @@ -3,7 +3,8 @@ maintenance: enabled: false service: - externalPort: 8080 + name: -maintenance # default service name would be Release.name-Chart.name-maintenance + externalPort: 8080 # default port 80 postgresql: enabled: false # do not forget tu update networkPolicy diff --git a/wb/Chart.yaml b/wb/Chart.yaml index 42fb1255..defa9f3c 100644 --- a/wb/Chart.yaml +++ b/wb/Chart.yaml @@ -1,7 +1,8 @@ -apiVersion: v1 -description: WaterButler is a Python web application for interacting with various file storage services via a single RESTful API +apiVersion: v2 name: wb -version: 0.9.1 +description: WaterButler is a Python web application for interacting with various file storage services via a single RESTful API +type: application +version: 1.0.0 keywords: - files - storage @@ -18,5 +19,18 @@ maintainers: - name: Uditi Mehta email: uditi@cos.io url: https://github.com/uditijmehta -engine: gotpl -tillerVersion: '>=2.7.0' +dependencies: + # - name: cos-common + # version: 1.0.0 + # repository: https://centerforopenscience.github.io/helm-charts/ + - name: cos-common + version: 1.0.0 + repository: "file://../cos-common" + - name: maintenance + version: 0.2.0 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: maintenance.enabled, global.maintenance.enabled + - name: redis + version: 1.1.3 + repository: https://centerforopenscience.github.io/helm-charts/ + condition: redis.enabled, global.redis.enabled diff --git a/wb/files/nginx.conf b/wb/files/nginx.conf new file mode 100644 index 00000000..400a9126 --- /dev/null +++ b/wb/files/nginx.conf @@ -0,0 +1,113 @@ +user nginx; +worker_processes {{ .Values.nginx.workerCount }}; + +load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; +{{- if .Values.nginx.vts.enabled }} +load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; +load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; +{{- end }} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; + access_log /var/log/nginx/access.log main; + + real_ip_header {{ .Values.nginx.realIpHeader }}; + real_ip_recursive {{ .Values.nginx.realIpRecursive }}; + {{- range .Values.nginx.proxySourceRanges }} + set_real_ip_from {{ . }}; + {{- end }} + + {{- if .Values.nginx.vts.enabled }} + geoip_country /etc/nginx/GeoIP.dat; + geoip_city /etc/nginx/GeoLiteCity.dat; + geoip_proxy_recursive on; + {{- range .Values.nginx.proxySourceRanges }} + geoip_proxy {{ . }}; + {{- end }} + + vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; + vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; + {{- end }} + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 620s; + keepalive_requests 10000; + types_hash_max_size 2048; + server_tokens off; + + gzip on; + gzip_proxied any; + gzip_disable "msie6"; + gzip_min_length 1400; + gzip_vary off; + gzip_buffers 4 32k; + gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + brotli on; + brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; + + {{- if .Values.nginx.vts.enabled }} + server { + listen {{ .Values.nginx.vts.internalPort }}; + server_name _; + + location /healthz { + access_log off; + return 200; + } + + location /nginx_status { + vhost_traffic_status_display; + vhost_traffic_status_display_format html; + } + } + {{- end }} + + server { + listen {{ .Values.main.http.containers.nginx.internalPort }}; + keepalive_timeout 620s; + client_max_body_size 9999m; + server_name _; + + if ($http_x_forwarded_proto = "http") { + return 301 https://$host$request_uri; + } + + location = /healthz { + access_log off; + return 200; + } + + location = /robots.txt { + alias /usr/share/nginx/html/robots.txt; + } + + location / { + add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; + add_header Expires "-1"; + add_header Pragma "no-cache"; + + proxy_buffering off; + proxy_request_buffering off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout {{ .Values.nginx.readTimeout }}s; + proxy_pass http://127.0.0.1:{{ .Values.main.http.containers.nginx.externalPort }}; + } + } +} diff --git a/wb/requirements.lock b/wb/requirements.lock deleted file mode 100644 index d7a399e5..00000000 --- a/wb/requirements.lock +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: -- name: maintenance - repository: https://centerforopenscience.github.io/helm-charts/ - version: 0.2.0 -- name: redis - repository: https://centerforopenscience.github.io/helm-charts/ - version: 1.1.3 -digest: sha256:252b9835080950ac504357281f207b11ac4d401e5962f451548c38e38d0a8b8d -generated: 2023-01-24T17:11:53.481645-05:00 diff --git a/wb/requirements.yaml b/wb/requirements.yaml deleted file mode 100644 index f49b5137..00000000 --- a/wb/requirements.yaml +++ /dev/null @@ -1,9 +0,0 @@ -dependencies: - - name: maintenance - version: 0.2.0 - repository: https://centerforopenscience.github.io/helm-charts/ - condition: maintenance.enabled, global.maintenance.enabled - - name: redis - version: 1.1.3 - repository: https://centerforopenscience.github.io/helm-charts/ - condition: redis.enabled, global.redis.enabled diff --git a/wb/templates/NOTES.txt b/wb/templates/NOTES.txt index 0a142d6e..17a43b24 100644 --- a/wb/templates/NOTES.txt +++ b/wb/templates/NOTES.txt @@ -1,17 +1,23 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "wb.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "wb.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "wb.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "wb.fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} +{{- $fullname := include "cos-common.fullname" (dict "root" . "name" "") | trim -}} +{{- $service := .Values.main.service -}} +{{- $externalPort := .Values.main.http.containers.nginx.externalPort -}} +{{- $internalPort := .Values.main.http.containers.nginx.internalPort -}} + +1. Main service: {{ $fullname }} +{{- if and $service (eq $service.enabled true) }} +{{- if contains "LoadBalancer" $service.type }} + Wait for the external IP, then: + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ $fullname }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ $externalPort }} +{{- else if contains "NodePort" $service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ $fullname }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "cos-common.chartName" (dict "root" .) }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:{{ $internalPort }} +{{- end }} +{{- else }} + Service disabled. {{- end }} diff --git a/wb/templates/_helpers.tpl b/wb/templates/_helpers.tpl deleted file mode 100644 index 833fc222..00000000 --- a/wb/templates/_helpers.tpl +++ /dev/null @@ -1,102 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "wb.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "wb.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified certificate name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "wb.certificate.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s-%s" .Release.Name $name .Values.certificate.name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified maintenance name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "maintenance.fullname" -}} -{{- $name := "maintenance" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified rabbitmq name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "rabbitmq.fullname" -}} -{{- $name := "rabbitmq" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Overridable deployment annotations -*/}} -{{- define "wb.deploymentAnnotations" -}} -checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} -checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} -{{- end -}} - -{{/* -Create a default fully qualified redis name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "redis.fullname" -}} -{{- $name := "redis" -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "redis.service" -}} -{{- $name := "redis" -}} -{{- printf "%s-%s.%s.%s" .Release.Name $name .Release.Namespace "svc.cluster.local" -}} -{{- end -}} - -{{- $rateLimitingEnabled := .Values.rateLimiting.enabled -}} - -{{- define "wb.environment" -}} -{{- if .Values.rateLimiting.enabled }} -- name: SERVER_CONFIG_ENABLE_RATE_LIMITING - value: "1" -{{- end }} -{{- if .Values.redis.enabled }} -- name: SERVER_CONFIG_REDIS_HOST - value: {{ template "redis.service" . }} -- name: SERVER_CONFIG_REDIS_PORT - value: {{ .Values.redis.service.port | quote }} -{{- if hasKey .Values.redis.secretEnvs "REDIS_PASSWORD" }} -- name: SERVER_CONFIG_REDIS_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "redis.fullname" . }} - key: REDIS_PASSWORD -{{- end }} -{{- end }} -{{- $fullname := include "wb.fullname" . -}} -{{- range $key, $value := .Values.configEnvs }} -- name: {{ $key }} - valueFrom: - configMapKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- range $key, $value := .Values.secretEnvs }} -- name: {{ $key }} - valueFrom: - secretKeyRef: - name: {{ $fullname }} - key: {{ $key }} -{{- end }} -{{- end -}} diff --git a/wb/templates/certificate-networkpolicy.yaml b/wb/templates/certificate-networkpolicy.yaml deleted file mode 100644 index 8b10d622..00000000 --- a/wb/templates/certificate-networkpolicy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if (and .Values.networkPolicy.enabled .Values.certificate.enabled) }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: "{{ template "wb.certificate.fullname" . }}" - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - policyTypes: - - Ingress - podSelector: - matchExpressions: - - {key: acme.cert-manager.io/http01-solver, operator: Exists} - ingress: - - from: [] -{{- end }} diff --git a/wb/templates/certificate.yaml b/wb/templates/certificate.yaml deleted file mode 100644 index 4f216f3b..00000000 --- a/wb/templates/certificate.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if and .Values.certificate.enabled .Values.certificate.createCert -}} -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "{{ template "wb.certificate.fullname" . }}" - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - component: "{{ .Values.certificate.name }}" - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - secretName: "{{ template "wb.certificate.fullname" . }}" - issuerRef: - name: {{ .Values.certificate.issuerRef.name }} - kind: {{ .Values.certificate.issuerRef.kind }} - commonName: {{ .Values.certificate.commonName }} - dnsNames: - {{- range .Values.certificate.dnsNames }} - - {{ . }} - {{- end }} - acme: - config: - - http01: - {{- if hasKey .Values.certificate.acmeConfig.http01 "ingress" }} - ingress: {{ .Values.certificate.acmeConfig.http01.ingress }} - {{- else }} - ingress: {{ template "wb.fullname" . }} - {{- end }} - domains: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} -{{- end -}} diff --git a/wb/templates/configmap.yaml b/wb/templates/configmap.yaml deleted file mode 100644 index 05bd63b5..00000000 --- a/wb/templates/configmap.yaml +++ /dev/null @@ -1,134 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -data: -{{- define "wb.inlineconfigs" }} -nginx.conf: |- - user nginx; - worker_processes {{ .Values.nginx.workerCount }}; - - load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; - {{- if .Values.nginx.vts.enabled }} - load_module /usr/lib/nginx/modules/ngx_http_geoip_module.so; - load_module /usr/lib/nginx/modules/ngx_http_vhost_traffic_status_module.so; - {{- end }} - - error_log /var/log/nginx/error.log warn; - pid /var/run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $upstream_cache_status $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ' - 'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"'; - access_log /var/log/nginx/access.log main; - - real_ip_header {{ .Values.nginx.realIpHeader }}; - real_ip_recursive {{ .Values.nginx.realIpRecursive }}; - {{- range .Values.nginx.proxySourceRanges }} - set_real_ip_from {{ . }}; - {{- end }} - - {{- if .Values.nginx.vts.enabled }} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - {{- range .Values.nginx.proxySourceRanges }} - geoip_proxy {{ . }}; - {{- end }} - - vhost_traffic_status_zone shared:vhost_traffic_status:{{ .Values.nginx.vts.statusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ .Values.nginx.vts.defaultFilterKey }}; - {{- end }} - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 620s; - keepalive_requests 10000; - types_hash_max_size 2048; - server_tokens off; - - gzip on; - gzip_proxied any; - gzip_disable "msie6"; - gzip_min_length 1400; - gzip_vary off; # Needs to be off because some files will not render properly when it's on (sets the Accept header) - gzip_buffers 4 32k; - gzip_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - brotli on; - brotli_types text/plain text/css image/svg+xml application/javascript application/x-javascript text/xml text/javascript application/json application/vnd.api+json; - - {{- if .Values.nginx.vts.enabled }} - server { - listen {{ .Values.nginx.vts.internalPort }}; - server_name _; - - location /healthz { - access_log off; - return 200; - } - - location /nginx_status { - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - } - } - {{- end }} - - server { - listen {{ .Values.service.internalPort }}; - keepalive_timeout 620s; - client_max_body_size 9999m; - server_name _; - - if ($http_x_forwarded_proto = "http") { - return 301 https://$host$request_uri; - } - - location = /healthz { - access_log off; - return 200; - } - - location = /robots.txt { - alias /usr/share/nginx/html/robots.txt; - } - - location / { - # Disable caching of application requests - add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate"; - add_header Expires "-1"; - add_header Pragma "no-cache"; - - proxy_buffering off; - proxy_request_buffering off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_read_timeout {{ .Values.nginx.readTimeout }}s; - proxy_pass http://127.0.0.1:{{ .Values.service.externalPort }}; - } - } - } -{{- end -}} - {{- range $key, $value := .Values.configEnvs }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- range $key, $value := merge .Values.configFiles (include "wb.inlineconfigs" . | fromYaml) ((.Files.Glob "files/*").AsConfig | fromYaml) }} - {{ $key }}: |- - {{- $value | nindent 4 }} - {{- end }} diff --git a/wb/templates/deployment.yaml b/wb/templates/deployment.yaml deleted file mode 100644 index 675ba422..00000000 --- a/wb/templates/deployment.yaml +++ /dev/null @@ -1,245 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - - replicas: {{ .Values.replicaCount }} - {{- if .Values.strategy }} - strategy: - {{- toYaml .Values.strategy | nindent 4 }} - {{- end }} - template: - metadata: - labels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - annotations: - {{- include "wb.deploymentAnnotations" . | nindent 8 }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - {{- if eq .Values.antiAffinity "hard" }} - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - {{- else if eq .Values.antiAffinity "soft" }} - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - {{- end }} - initContainers: - - name: chown - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - chown -R www-data:www-data /pickles - volumeMounts: - - name: pickles - mountPath: /pickles - containers: - - name: nginx - image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" - imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} - command: - - nginx - - -c - - /etc/nginx/nginx.conf - - -g - - daemon off; - ports: - - name: http - containerPort: {{ .Values.service.internalPort }} - readinessProbe: - httpGet: - path: /healthz - port: {{ .Values.service.internalPort }} - volumeMounts: - - name: config - subPath: nginx.conf - mountPath: /etc/nginx/nginx.conf - readOnly: true - - name: config - subPath: robots.txt - mountPath: /usr/share/nginx/html/robots.txt - readOnly: true - {{- if .Values.nginx.resources }} - resources: - {{- toYaml .Values.nginx.resources | nindent 12 }} - {{- end }} - - name: tornado - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - set -e - PREFIX='' - if [ -f /code/newrelic.ini ]; then - PREFIX='newrelic-admin run-program' - fi - $PREFIX gosu www-data invoke server - env: - {{- include "wb.environment" . | nindent 12 }} - - name: ENV - value: kube - - name: TASKS_CONFIG_BROKER_URL - value: "amqp://{{ .Values.rabbitmq.rabbitmqUsername }}:{{ .Values.rabbitmq.rabbitmqPassword }}@127.0.0.1:{{ .Values.rabbitmq.rabbitmqNodePort }}/{{ .Values.rabbitmq.rabbitmqVhost }}" - {{- range $key, $value := .Values.tornado.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - ports: - - name: http - containerPort: {{ .Values.service.externalPort }} - readinessProbe: - httpGet: - path: /status - port: {{ .Values.service.externalPort }} - volumeMounts: - - name: secret - subPath: settings.json - mountPath: /home/.cos/waterbutler-kube.json - readOnly: true - - name: data - mountPath: /data - - name: pickles - mountPath: /pickles - {{- if .Values.tornado.volumeMounts }} - {{- toYaml .Values.tornado.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.tornado.resources }} - resources: - {{- toYaml .Values.tornado.resources | nindent 12 }} - {{- end }} - - name: worker - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -c - - |- - set -e - PREFIX='' - if [ -f /code/newrelic.ini ]; then - PREFIX='newrelic-admin run-program' - fi - $PREFIX gosu www-data celery -A waterbutler.tasks.app worker \ - -c "{{ .Values.worker.concurrency }}" -l "{{ .Values.worker.logLevel }}" \ - -n worker.%h --without-gossip - {{- if .Values.worker.maxTasksPerChild }} --max-tasks-per-child "{{ .Values.worker.maxTasksPerChild }}" {{- end }} - env: - {{- include "wb.environment" . | nindent 12 }} - - name: ENV - value: kube - - name: TASKS_CONFIG_BROKER_URL - value: "amqp://{{ .Values.rabbitmq.rabbitmqUsername }}:{{ .Values.rabbitmq.rabbitmqPassword }}@127.0.0.1:{{ .Values.rabbitmq.rabbitmqNodePort }}/{{ .Values.rabbitmq.rabbitmqVhost }}" - {{- range $key, $value := .Values.worker.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - volumeMounts: - - name: secret - subPath: settings.json - mountPath: /home/.cos/waterbutler-kube.json - readOnly: true - - name: data - mountPath: /data - - name: pickles - mountPath: /pickles - {{- if .Values.worker.volumeMounts }} - {{- toYaml .Values.worker.volumeMounts | nindent 12 }} - {{- end }} - {{- if .Values.worker.resources }} - resources: - {{- toYaml .Values.worker.resources | nindent 12 }} - {{- end }} - - name: rabbitmq - image: "{{ .Values.rabbitmq.image.repository }}:{{ .Values.rabbitmq.image.tag }}" - imagePullPolicy: {{ .Values.rabbitmq.image.pullPolicy }} - env: - - name: RABBITMQ_USERNAME - value: {{ default "" .Values.rabbitmqUsername | quote }} - - name: RABBITMQ_PASSWORD - value: {{ default "" .Values.rabbitmqPassword | quote }} - - name: RABBITMQ_NODE_PORT_NUMBER - value: {{ default "5672" .Values.rabbitmq.rabbitmqNodePort | quote }} - - name: RABBITMQ_NODE_TYPE - value: {{ default "stats" .Values.rabbitmq.rabbitmqNodeType | quote }} - - name: RABBITMQ_NODE_NAME - value: {{ printf "%s@%s" (default "rabbit" .Values.rabbitmq.rabbitmqNodeName) "localhost" | quote }} - - name: RABBITMQ_CLUSTER_NODE_NAME - value: {{ default "" .Values.rabbitmq.rabbitmqClusterNodeName | quote }} - - name: RABBITMQ_VHOST - value: {{ default "/" .Values.rabbitmq.rabbitmqVhost | quote }} - - name: RABBITMQ_MANAGER_PORT_NUMBER - value: {{ default "15672" .Values.rabbitmq.rabbitmqManagerPort | quote }} - ports: - - name: amqp - containerPort: {{ default "5672" .Values.rabbitmq.rabbitmqNodePort }} - - name: stats - containerPort: {{ default "15672" .Values.rabbitmq.rabbitmqManagerPort }} - livenessProbe: - exec: - command: - - rabbitmqctl - - status - initialDelaySeconds: 120 - timeoutSeconds: 5 - failureThreshold: 6 - readinessProbe: - exec: - command: - - rabbitmqctl - - status - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 5 - volumeMounts: - - name: rabbitmq-data - mountPath: /bitnami/rabbitmq - {{- if .Values.rabbitmq.resources }} - resources: - {{- toYaml .Values.rabbitmq.resources | nindent 12 }} - {{- end }} - volumes: - - name: rabbitmq-data - emptyDir: {} - - name: data - emptyDir: {} - - name: pickles - emptyDir: {} - - name: config - configMap: - name: {{ template "wb.fullname" . }} - - name: secret - secret: - secretName: {{ template "wb.fullname" . }} - {{- if .Values.nodeSelector }} - nodeSelector: - {{- toYaml .Values.nodeSelector | nindent 8 }} - {{- end }} diff --git a/wb/templates/hpa.yaml b/wb/templates/hpa.yaml deleted file mode 100644 index 84e7cfcf..00000000 --- a/wb/templates/hpa.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.horizontalPodAutoscaler.enabled -}} -apiVersion: autoscaling/v1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ template "wb.fullname" . }} - minReplicas: {{ .Values.replicaCount }} - maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }} - targetCPUUtilizationPercentage: {{ .Values.horizontalPodAutoscaler.targetCPUUtilizationPercentage }} -{{- end -}} diff --git a/wb/templates/ingress.yaml b/wb/templates/ingress.yaml deleted file mode 100644 index 812f01c1..00000000 --- a/wb/templates/ingress.yaml +++ /dev/null @@ -1,68 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $serviceName := include "wb.fullname" . -}} -{{- $servicePort := .Values.service.externalPort -}} -{{- $ingressPaths := .Values.ingress.paths -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} -spec: - rules: - {{- if .Values.maintenance.enabled }} - {{- $serviceName := include "maintenance.fullname" . -}} - {{- $servicePort := .Values.maintenance.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- else -}} - {{- $serviceName := include "wb.fullname" . -}} - {{- $servicePort := .Values.service.externalPort -}} - {{- range .Values.ingress.hosts }} - - host: {{ . }} - http: - paths: - {{- range $ingressPaths }} - - path: {{ . }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end -}} - {{- end -}} - {{- end -}} - {{- if (or .Values.ingress.tls (and .Values.certificate.enabled .Values.certificate.tls)) }} - tls: - {{- if .Values.ingress.tls }} - {{- toYaml .Values.ingress.tls | nindent 4 }} - {{- end -}} - {{- if (and .Values.certificate.enabled .Values.certificate.tls) }} - - secretName: "{{ template "wb.certificate.fullname" . }}" - hosts: - {{- range .Values.certificate.acmeConfig.domains }} - - {{ . }} - {{- end }} - {{- end -}} - {{- end -}} -{{- end -}} diff --git a/wb/templates/main.yaml b/wb/templates/main.yaml new file mode 100644 index 00000000..544451fb --- /dev/null +++ b/wb/templates/main.yaml @@ -0,0 +1,10 @@ +{{- include "cos-common.configmap" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.service" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.ingress" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.hpa" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pdb" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.networkpolicy" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.pvc" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.certificate" (dict "root" . "name" "cert" "values" .Values.main) }} diff --git a/wb/templates/networkpolicy.yaml b/wb/templates/networkpolicy.yaml deleted file mode 100644 index 7fa5b92e..00000000 --- a/wb/templates/networkpolicy.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - podSelector: - matchLabels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - ingress: - - ports: - - port: {{ .Values.service.internalPort }} - {{- if not .Values.networkPolicy.allowExternal }} - from: - - podSelector: - matchLabels: - {{ template "wb.fullname" . }}-client: "true" - {{- end }} - {{- if .Values.nginx.vts.enabled }} - - ports: - - port: {{ .Values.nginx.vts.internalPort }} - {{- end }} - egress: {{- toYaml .Values.networkPolicy.egress | nindent 4 }} -{{- end }} diff --git a/wb/templates/pdb.yaml b/wb/templates/pdb.yaml deleted file mode 100644 index 806b6217..00000000 --- a/wb/templates/pdb.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.budget.minAvailable -}} -{{- if .Capabilities.APIVersions.Has "policy/v1" -}} -apiVersion: policy/v1 -{{- else}} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: "{{ template "wb.fullname" . }}" - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} - minAvailable: {{ .Values.budget.minAvailable }} -{{- end -}} diff --git a/wb/templates/secret.yaml b/wb/templates/secret.yaml deleted file mode 100644 index 50a2a45e..00000000 --- a/wb/templates/secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: -{{- range $key, $value := .Values.secretEnvs }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} -{{- range $key, $value := .Values.secretFiles }} - {{ $key }}: {{ $value | b64enc | quote }} -{{- end }} diff --git a/wb/templates/service.yaml b/wb/templates/service.yaml deleted file mode 100644 index cd2e09c0..00000000 --- a/wb/templates/service.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "wb.fullname" . }} - labels: - app: {{ template "wb.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - type: {{ .Values.service.type }} - {{- if eq .Values.service.type "LoadBalancer" }} - {{- if .Values.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.service.loadBalancerIP | quote }} - {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: - {{- range .Values.service.loadBalancerSourceRanges }} - - {{ . | quote }} - {{- end }} - {{- end }} - {{- end }} - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - ports: - - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} - protocol: TCP - name: {{ .Values.service.name }} - selector: - app: {{ template "wb.name" . }} - release: {{ .Release.Name }} diff --git a/wb/values.yaml b/wb/values.yaml index 32a35e15..44af6af7 100644 --- a/wb/values.yaml +++ b/wb/values.yaml @@ -1,40 +1,12 @@ -# Default values for wb. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. -replicaCount: 1 - -image: +### ------- Global or reusable parts across values.yaml ------- +appImage: repository: quay.io/centerforopenscience/waterbutler tag: develop pullPolicy: Always -antiAffinity: soft - -# strategy: -# rollingUpdate: -# maxSurge: 25% -# maxUnavailable: 25% -# type: RollingUpdate - -budget: - minAvailable: 0 - nginx: workerCount: 1 - image: - repository: quay.io/centerforopenscience/nginx - tag: latest - pullPolicy: Always - resources: {} - # limits: - # cpu: 1 - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi proxySourceRanges: [] - # - 130.211.0.0/22 - # - 35.191.0.0/16 realIpHeader: X-Real-IP realIpRecursive: "off" vts: @@ -42,311 +14,535 @@ nginx: internalPort: 18080 statusZoneSize: 10m defaultFilterKey: "$geoip_country_code country::*" - readTimeout: 60 # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout - -tornado: - resources: {} - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - volumeMounts: [] - env: {} - -worker: - concurrency: 2 - logLevel: INFO - maxTasksPerChild: 5 - resources: {} - # limits: - # cpu: 2 - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - volumeMounts: [] - env: {} - -horizontalPodAutoscaler: - enabled: false - maxReplicas: 3 - targetCPUUtilizationPercentage: 90 - -service: - name: http - type: ClusterIP - # If using type: LoadBalancer - #loadBalancerIP: 0.0.0.0 - #loadBalancerSourceRanges: - # - 0.0.0.0/0 - externalPort: 7777 - internalPort: 80 - -ingress: - enabled: false - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - # ingress.kubernetes.io/proxy-body-size: 9999m - # nginx.org/client-max-body-size: 9999m - paths: - - / - hosts: - # - staging-files.osf.io - tls: - # - secretName: osf-io-tls - # hosts: - # - staging-files.osf.io - -certificate: - enabled: false - createCert: false - name: cert - # WORKAROUND: Ingress deploy blocked to GLBC due to race condition w/ missing TLS certificate - # - Issue: https://github.com/jetstack/cert-manager/issues/606 - # - PR: https://github.com/kubernetes/ingress-gce/pull/388 - tls: true - # issuerRef: - # name: letsencrypt-prod - # kind: ClusterIssuer - # commonName: example.org - # dnsNames: - # - example.org - # - subdomain.example.org - # acmeConfig: - # http01: {} - # # ingress: '' - # domains: - # - example.org - # - subdomain.example.org - -networkPolicy: - enabled: false - # Allows external access to the pod, otherwise access is restricted to - # clients with the explicit label. - allowExternal: true - egress: {} - # - to: - # - namespaceSelector: {} - # ports: - # - port: 53 # dns - # protocol: TCP - # - port: 53 # dns - # protocol: UDP - # - to: - # - ipBlock: - # cidr: 0.0.0.0/0 - # except: - # - 10.0.0.0/8 - # - 172.16.0.0/12 - # - 192.168.0.0/16 - # ports: - # - port: 80 # http - # protocol: TCP - # - port: 443 # https - # protocol: TCP - -configEnvs: {} - -configFiles: {} - -secretEnvs: {} - -secretFiles: - settings.json: |- - { - "ANALYTICS": { - "MFR_DOMAIN": "https://staging-mfr.osf.io", - "KEEN": { - "PRIVATE": { - "PROJECT_ID": "", - "WRITE_KEY": "" - }, - "PUBLIC": { - "PROJECT_ID": "", - "WRITE_KEY": "" - } - } - }, - "GITHUB_PROVIDER_CONFIG": { - "MOVE_MESSAGE": "Moved via the Open Science Framework", - "COPY_MESSAGE": "Copied via the Open Science Framework", - "UPLOAD_FILE_MESSAGE": "Added via the Open Science Framework", - "UPDATE_FILE_MESSAGE": "Updated via the Open Science Framework", - "DELETE_FILE_MESSAGE": "Deleted via the Open Science Framework", - "DELETE_FOLDER_MESSAGE": "Deleted via the Open Science Framework" - }, - "OSFSTORAGE_PROVIDER_CONFIG": { - "FILE_PATH_PENDING": "/data/pending", - "FILE_PATH_COMPLETE": "/data/complete", - "HMAC_SECRET": "", - "RUN_TASKS": true - }, - "SERVER_CONFIG": { - "DOMAIN": "https://staging-files.osf.io", - "ADDRESS": "0.0.0.0", - "PORT": 7777, - "DEBUG": false, - "XHEADERS": true, - "CORS_ALLOW_ORIGIN": [ - "https://staging.osf.io", - "https://staging-mfr.osf.io" - ], - "HMAC_SECRET": "", - "MAX_BUFFER_SIZE": 157286400, - "AUTH_HANDLERS": [ - "osf" - ] - }, - "OSF_AUTH_CONFIG": { - "API_URL": "https://staging.osf.io/api/v1/files/auth/", - "JWE_SALT": "", - "JWE_SECRET": "", - "JWT_SECRET": "" - }, - "TASKS_CONFIG": { - "BROKER_URL": "amqp://guest:guest@127.0.0.1:5672//", - "ADHOC_BACKEND_PATH": "/pickles" - }, - "SENTRY_DSN": "", - "LOGGING": { - "version": 1, - "disable_existing_loggers": false, - "formatters": { - "defaultFormatter": { - "()": "waterbutler.core.logging.MaskFormatter", - "format": "[%(asctime)s][%(levelname)s][%(name)s]: %(message)s", - "pattern": "(?<=cookie=)(.*?)(?=&|$)", - "mask": "***" - } - }, - "handlers": { - "consoleHandler": { - "class": "logging.StreamHandler", - "level": "INFO", - "formatter": "defaultFormatter" - } - }, - "loggers": { - "": { - "handlers": [ - "consoleHandler" - ], - "level": "INFO", - "propagate": false - } - }, - "root": { - "level": "INFO", - "handlers": [ - "consoleHandler" - ] - } - } - } - -maintenance: - enabled: false + readTimeout: 60 rabbitmq: - image: - repository: rabbitmq - tag: 3-management - pullPolicy: Always - rabbitmqUsername: guest rabbitmqPassword: guest rabbitmqNodePort: 5672 rabbitmqNodeType: stats rabbitmqNodeName: rabbit@localhost - # rabbitmqClusternodename: + rabbitmqClusterNodeName: "" rabbitmqVhost: / rabbitmqManagerPort: 15672 - resources: - requests: - memory: 256Mi - cpu: 100m - -rateLimiting: - enabled: false - redis: - enabled: true - + enabled: false image: repository: redis tag: alpine pullPolicy: IfNotPresent - secretEnvs: {} - # REDIS_PASSWORD: '' - - ## Redis command arguments - ## - ## Can be used to specify command line arguments, for example: - ## - ## args: - ## - --maxmemory 200mb - ## - --maxmemory-policy volatile-ttl - args: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## + args: [] persistence: enabled: false - - ## A manually managed Persistent Volume and Claim - ## Requires persistence.enabled: true - ## If defined, PVC must be created manually before volume will be bound - # existingClaim: - - ## redis data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" accessMode: ReadWriteOnce size: 8Gi - - ## Configure resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## resources: {} - #limits: - # cpu: 100m - # memory: 256Mi - #requests: - # cpu: 100m - # memory: 256Mi - service: type: ClusterIP - loadBalancerIP: - #annotations: {} - # If using type: LoadBalancer - #loadBalancerIP: 0.0.0.0 - #loadBalancerSourceRanges: - # - 0.0.0.0/0 port: 6379 externalIPs: [] + networkPolicy: + enabled: false + allowExternal: true + +maintenance: + enabled: false + service: + name: -maintenance # default service name would be Release.name-Chart.name-maintenance + + +## Remember that full name for all objects is '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' +## or in other form current naming is Release.Name-Chart.Name + +## =============== MAIN Component =============== +main: + enabled: true + + replicas: 1 + + http: + containers: + nginx: + internalPort: 80 + externalPort: 7777 + serviceType: ClusterIP + + +# ------- Configuration follows for containerName: nginx ------- + image: + repository: quay.io/centerforopenscience/nginx + tag: latest + pullPolicy: Always + + containerName: nginx + + command: + - nginx + - -c + - /etc/nginx/nginx.conf + - -g + - daemon off; + + env: [] + + envFrom: [] + + probes: + readiness: + httpGet: + path: /healthz + port: "{{ .Values.main.http.containers.nginx.internalPort }}" + + ports: + - name: http + containerPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + protocol: TCP + + volumeMounts: + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + readOnly: true + - name: config + mountPath: /usr/share/nginx/html/robots.txt + subPath: robots.txt + readOnly: true + + resources: {} + + +# ------- Init containers ------- + initContainers: + - name: chown + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/sh + - -c + - chown -R www-data:www-data /pickles + volumeMounts: + - name: pickles + mountPath: /pickles + + +# ------- Additional containers ------- + tornado: + volumeMounts: + - name: secret + subPath: settings.json + mountPath: /home/.cos/waterbutler-kube.json + readOnly: true + - name: data + mountPath: /data + - name: pickles + mountPath: /pickles + resources: {} + + worker: + volumeMounts: + - name: secret + subPath: settings.json + mountPath: /home/.cos/waterbutler-kube.json + readOnly: true + - name: data + mountPath: /data + - name: pickles + mountPath: /pickles + resources: {} + concurrency: 2 + logLevel: INFO + maxTasksPerChild: 5 + + rabbitmq: + image: + repository: rabbitmq + tag: 3-management + pullPolicy: Always + volumeMounts: + - name: rabbitmq-data + mountPath: /bitnami/rabbitmq + resources: + requests: + memory: 256Mi + cpu: 100m + + additionalContainers: + - name: tornado + inheritVolumeMountsFrom: tornado + inheritResourcesFrom: tornado + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/sh + - -c + - |- + set -e + PREFIX='' + if [ -f /code/newrelic.ini ]; then + PREFIX='newrelic-admin run-program' + fi + $PREFIX gosu www-data invoke server + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + ports: + - name: http + containerPort: "{{ .Values.main.http.containers.nginx.externalPort }}" + readinessProbe: + httpGet: + path: /status + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + + - name: worker + inheritVolumeMountsFrom: worker + inheritResourcesFrom: worker + image: "{{ .Values.appImage.repository }}:{{ .Values.appImage.tag }}" + imagePullPolicy: "{{ .Values.appImage.pullPolicy }}" + command: + - /bin/sh + - -c + - |- + set -e + PREFIX='' + if [ -f /code/newrelic.ini ]; then + PREFIX='newrelic-admin run-program' + fi + $PREFIX gosu www-data celery -A waterbutler.tasks.app worker \ + -c "{{ .Values.main.worker.concurrency }}" -l "{{ .Values.main.worker.logLevel }}" \ + -n worker.%h --without-gossip + {{- if .Values.main.worker.maxTasksPerChild }} --maxtasksperchild "{{ .Values.main.worker.maxTasksPerChild }}" {{- end }} + envFrom: + - configMapRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + - secretRef: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "common-env") | trim }}' + + - name: rabbitmq + inheritVolumeMountsFrom: rabbitmq + inheritResourcesFrom: rabbitmq + image: "{{ .Values.main.rabbitmq.image.repository }}:{{ .Values.main.rabbitmq.image.tag }}" + imagePullPolicy: "{{ .Values.main.rabbitmq.image.pullPolicy }}" + env: + - name: RABBITMQ_USERNAME + value: "{{ .Values.rabbitmq.rabbitmqUsername }}" + - name: RABBITMQ_PASSWORD + value: "{{ .Values.rabbitmq.rabbitmqPassword }}" + - name: RABBITMQ_NODE_PORT_NUMBER + value: "{{ .Values.rabbitmq.rabbitmqNodePort }}" + - name: RABBITMQ_NODE_TYPE + value: "{{ .Values.rabbitmq.rabbitmqNodeType }}" + - name: RABBITMQ_NODE_NAME + value: "{{ printf \"%s@%s\" .Values.rabbitmq.rabbitmqNodeName \"localhost\" }}" + - name: RABBITMQ_CLUSTER_NODE_NAME + value: "{{ .Values.rabbitmq.rabbitmqClusterNodeName }}" + - name: RABBITMQ_VHOST + value: "{{ .Values.rabbitmq.rabbitmqVhost }}" + - name: RABBITMQ_MANAGER_PORT_NUMBER + value: "{{ .Values.rabbitmq.rabbitmqManagerPort }}" + ports: + - name: amqp + containerPort: "{{ .Values.rabbitmq.rabbitmqNodePort }}" + - name: stats + containerPort: "{{ .Values.rabbitmq.rabbitmqManagerPort }}" + livenessProbe: + exec: + command: + - rabbitmqctl + - status + initialDelaySeconds: 120 + timeoutSeconds: 5 + failureThreshold: 6 + readinessProbe: + exec: + command: + - rabbitmqctl + - status + initialDelaySeconds: 10 + timeoutSeconds: 3 + periodSeconds: 5 + + sidecars: [] + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: rabbitmq-data + emptyDir: {} + - name: data + emptyDir: {} + - name: pickles + emptyDir: {} + - name: config + configMap: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + additionalVolumes: [] + + + # ------- Affinity configuration ------- + affinity: {} + + additionalAffinities: + - podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app.kubernetes.io/name: "{{ .Chart.Name }}" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/component: "{{ .Chart.Name }}" # in this case component name = chart name, because we leave component name in main.yaml empty. + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/main-config: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "configmap") }}' + checksum/main-config-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "configmap") }}' + checksum/main-secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + checksum/main-secret-common-env: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "common-env" "values" .Values.main "resource" "secret") }}' + + +# ------- Service configuration ------- +# service name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + service: + enabled: true + type: "{{ .Values.main.http.containers.nginx.serviceType }}" + ports: + - name: http + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + targetPort: "{{ .Values.main.http.containers.nginx.internalPort }}" + + +# ------- Ingress configuration ------- +# ingress name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + ingress: + enabled: true + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + primary: + - chart-example.local + rules: + - name: main + includeForPrimaryHost: true + includeForSecondaryHost: false + pathType: ImplementationSpecific + service: + name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + port: "{{ .Values.main.http.containers.nginx.externalPort }}" + paths: + - / + tls: [] + # - secretName: secret_name + # hosts: + # - chart-example.local + + +# ------- Certificate configuration ------- (if we want to create Certificate object) +# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + certificate: + enabled: false + # secretName: secret-with-cert # default secret name is certificate name + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + commonName: example.org + dnsNames: + - example.org + acmeConfig: + http01: + ingress: 'test' + domains: + - example.org + + +# ---------- HPA configuration ---------- + hpa: + enabled: false + minReplicas: "{{ .Values.main.replicas }}" + maxReplicas: 3 + metrics: [] + behavior: {} + +# ---------- PDB configuration ---------- + pdb: + enabled: false + minAvailable: 0 + + +# ------- Network Policy configuration ------- +# Network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' networkPolicy: - ## Enable creation of NetworkPolicy resources. - ## enabled: false + ingressRules: + - ports: + - port: "{{ .Values.main.http.containers.nginx.internalPort }}" + from: + - podSelector: + matchLabels: + '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}-client': "true" + - ports: + - port: "{{ .Values.nginx.vts.internalPort }}" + egressRules: [] + + +# Additional network Policy name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalNetworkPolicies: + - name: cert-solver + enabled: false + podSelector: + matchExpressions: + - key: acme.cert-manager.io/http01-solver + operator: Exists + ingressRules: + - from: [] + + +# ------- ConfigMap configuration ------- +# ConfigMap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + configMap: + enabled: true + tpl: true + data: + nginx.conf: | + {{ tpl (.Files.Get "files/nginx.conf") (dict "Values" .Values "root" .) }} + robots.txt: |- + {{ .Files.Get "files/robots.txt" }} + + +# configmap name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalConfigMaps: + - name: common-env + enabled: true + tpl: true + data: + ## Uncomment when rateLimiting enabled <---- + # SERVER_CONFIG_ENABLE_RATE_LIMITING: "1" + ENV: kube + TASKS_CONFIG_BROKER_URL: "amqp://{{ .Values.rabbitmq.rabbitmqUsername }}:{{ .Values.rabbitmq.rabbitmqPassword }}@127.0.0.1:{{ .Values.rabbitmq.rabbitmqNodePort }}/{{ .Values.rabbitmq.rabbitmqVhost }}" + ## If Redis enabled <---- + # SERVER_CONFIG_REDIS_HOST: "{{ .Release.Name }}-redis.{{ .Release.Namespace }}.svc.cluster.local }}" + # SERVER_CONFIG_REDIS_PORT: "{{ .Values.redis.service.port }}" + + +# ------- Secrets configuration ------- +# Secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: true + data: + settings.json: |- + { + "ANALYTICS": { + "MFR_DOMAIN": "https://staging-mfr.osf.io", + "KEEN": { + "PRIVATE": { + "PROJECT_ID": "", + "WRITE_KEY": "" + }, + "PUBLIC": { + "PROJECT_ID": "", + "WRITE_KEY": "" + } + } + }, + "GITHUB_PROVIDER_CONFIG": { + "MOVE_MESSAGE": "Moved via the Open Science Framework", + "COPY_MESSAGE": "Copied via the Open Science Framework", + "UPLOAD_FILE_MESSAGE": "Added via the Open Science Framework", + "UPDATE_FILE_MESSAGE": "Updated via the Open Science Framework", + "DELETE_FILE_MESSAGE": "Deleted via the Open Science Framework", + "DELETE_FOLDER_MESSAGE": "Deleted via the Open Science Framework" + }, + "OSFSTORAGE_PROVIDER_CONFIG": { + "FILE_PATH_PENDING": "/data/pending", + "FILE_PATH_COMPLETE": "/data/complete", + "HMAC_SECRET": "", + "RUN_TASKS": true + }, + "SERVER_CONFIG": { + "DOMAIN": "https://staging-files.osf.io", + "ADDRESS": "0.0.0.0", + "PORT": 7777, + "DEBUG": false, + "XHEADERS": true, + "CORS_ALLOW_ORIGIN": [ + "https://staging.osf.io", + "https://staging-mfr.osf.io" + ], + "HMAC_SECRET": "", + "MAX_BUFFER_SIZE": 157286400, + "AUTH_HANDLERS": [ + "osf" + ] + }, + "OSF_AUTH_CONFIG": { + "API_URL": "https://staging.osf.io/api/v1/files/auth/", + "JWE_SALT": "", + "JWE_SECRET": "", + "JWT_SECRET": "" + }, + "TASKS_CONFIG": { + "BROKER_URL": "amqp://guest:guest@127.0.0.1:5672//", + "ADHOC_BACKEND_PATH": "/pickles" + }, + "SENTRY_DSN": "", + "LOGGING": { + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "defaultFormatter": { + "()": "waterbutler.core.logging.MaskFormatter", + "format": "[%(asctime)s][%(levelname)s][%(name)s]: %(message)s", + "pattern": "(?<=cookie=)(.*?)(?=&|$)", + "mask": "***" + } + }, + "handlers": { + "consoleHandler": { + "class": "logging.StreamHandler", + "level": "INFO", + "formatter": "defaultFormatter" + } + }, + "loggers": { + "": { + "handlers": [ + "consoleHandler" + ], + "level": "INFO", + "propagate": false + } + }, + "root": { + "level": "INFO", + "handlers": [ + "consoleHandler" + ] + } + } + } - ## The Policy model to apply. When set to false, only pods with the correct - ## client label will have network access to the port Redis is listening - ## on. When true, Redis will accept connections from any source - ## (with the correct destination port). - ## - allowExternal: true - +# secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + name + additionalSecrets: + - name: common-env + enabled: true + includeTls: false + data: {} + # If Redis enabled <---- + # SERVER_CONFIG_REDIS_PASSWORD: superpass + + +# ------- Selectors and etc. ------- + nodeSelector: {} + tolerations: [] From 60c7cb434260cbdb9c759f3f795e7b25c7e1e553 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Mon, 5 Jan 2026 14:11:33 +0200 Subject: [PATCH 10/11] Add cerebro chart --- cerebro/Chart.yaml | 19 ++- cerebro/templates/_helpers.tpl | 16 -- cerebro/templates/deployment.yaml | 65 -------- cerebro/templates/main.yaml | 2 + cerebro/templates/secret.yaml | 26 --- cerebro/values.yaml | 265 ++++++++++++++++++------------ cos-common/README.md | 2 +- cos-common/templates/_helpers.tpl | 2 +- cos-common/templates/_secret.tpl | 14 +- cos-common/values.schema.json | 12 ++ 10 files changed, 199 insertions(+), 224 deletions(-) mode change 100755 => 100644 cerebro/Chart.yaml delete mode 100644 cerebro/templates/_helpers.tpl delete mode 100644 cerebro/templates/deployment.yaml create mode 100644 cerebro/templates/main.yaml delete mode 100644 cerebro/templates/secret.yaml diff --git a/cerebro/Chart.yaml b/cerebro/Chart.yaml old mode 100755 new mode 100644 index 87101ee7..c3f93084 --- a/cerebro/Chart.yaml +++ b/cerebro/Chart.yaml @@ -1,9 +1,11 @@ +apiVersion: v2 name: cerebro -home: https://github.com/lmenezes/cerebro -apiVersion: v1 -version: 0.4.0 -description: cerebro is an open source(MIT License) elasticsearch web admin tool built using Scala, Play Framework, AngularJS and Bootstrap. -icon: https://github.com/lmenezes/cerebro/raw/master/public/img/logo.png +description: Elasticsearch web admin tool +type: application +version: 1.0.0 +keywords: + - cerebro + - elasticsearch sources: - https://github.com/lmenezes/cerebro maintainers: @@ -13,3 +15,10 @@ maintainers: - name: Matt Clark email: mattclark@cos.io url: https://github.com/mattclark +dependencies: + - name: cos-common + version: 1.0.0 + repository: https://centerforopenscience.github.io/helm-charts/ + # - name: cos-common + # version: 1.0.0 + # repository: "file://../cos-common" diff --git a/cerebro/templates/_helpers.tpl b/cerebro/templates/_helpers.tpl deleted file mode 100644 index 891a9241..00000000 --- a/cerebro/templates/_helpers.tpl +++ /dev/null @@ -1,16 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "cerebro.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "cerebro.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} diff --git a/cerebro/templates/deployment.yaml b/cerebro/templates/deployment.yaml deleted file mode 100644 index 5052c68c..00000000 --- a/cerebro/templates/deployment.yaml +++ /dev/null @@ -1,65 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "cerebro.fullname" . }} - labels: - app: {{ template "cerebro.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: {{ template "cerebro.name" . }} - release: {{ .Release.Name }} - replicas: 1 - template: - metadata: - labels: - app: {{ template "cerebro.name" . }} - release: {{ .Release.Name }} - annotations: - checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} - spec: - affinity: - {{- if .Values.additionalAffinities }} - {{- toYaml .Values.additionalAffinities | nindent 8 }} - {{- end }} - containers: - - name: cerebro - env: - - name: JAVA_OPTS - value: "-Djava.net.preferIPv4Stack=true -Xms{{ .Values.heapSize }} -Xmx{{ .Values.heapSize }}" - resources: - {{- toYaml .Values.resources | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ default "" .Values.image.pullPolicy | quote }} - ports: - - containerPort: 9000 - name: http - volumeMounts: - - mountPath: /opt/cerebro/conf/application.conf - name: secret - subPath: application.conf - {{- if .Values.tls.enabled }} - {{- if hasKey .Values.tls "files" }} - {{- range $key := keys .Values.tls.files }} - - mountPath: /certs/{{ $key }} - name: secret - subPath: certs-{{ $key }} - readOnly: true - {{- end }} - {{- end }} - {{- if hasKey .Values.tls "base64Files" }} - {{- range $key := keys .Values.tls.base64Files }} - - mountPath: /certs/{{ $key }} - name: secret - subPath: certs-{{ $key }} - readOnly: true - {{- end }} - {{- end }} - {{- end }} - volumes: - - name: secret - secret: - secretName: {{ template "cerebro.fullname" . }} diff --git a/cerebro/templates/main.yaml b/cerebro/templates/main.yaml new file mode 100644 index 00000000..8167663f --- /dev/null +++ b/cerebro/templates/main.yaml @@ -0,0 +1,2 @@ +{{- include "cos-common.secret" (dict "root" . "name" "" "values" .Values.main) }} +{{- include "cos-common.deployment" (dict "root" . "name" "" "values" .Values.main) }} diff --git a/cerebro/templates/secret.yaml b/cerebro/templates/secret.yaml deleted file mode 100644 index 9d5ef63d..00000000 --- a/cerebro/templates/secret.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "cerebro.fullname" . }} - labels: - app: {{ template "cerebro.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: - {{- range $key, $value := .Values.secrets }} - {{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- if .Values.tls.enabled }} - {{- if hasKey .Values.tls "files" }} - {{- range $key, $value := .Values.tls.files }} - certs-{{ $key }}: {{ $value | b64enc | quote }} - {{- end }} - {{- end }} - {{- if hasKey .Values.tls "base64Files" }} - {{- range $key, $value := .Values.tls.base64Files }} - certs-{{ $key }}: {{ $value | nospace | quote }} - {{- end }} - {{- end }} - {{- end }} diff --git a/cerebro/values.yaml b/cerebro/values.yaml index 60ee0028..84aac819 100644 --- a/cerebro/values.yaml +++ b/cerebro/values.yaml @@ -1,107 +1,160 @@ -# Default values for elasticsearch. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -image: - repository: quay.io/centerforopenscience/cerebro - tag: master # refers to v0.7 - pullPolicy: Always - -heapSize: "128m" - -resources: - limits: - cpu: "1" - memory: "512Mi" - requests: - cpu: "25m" - memory: "256Mi" - -tls: - enabled: false - #files: - # # Certificate Authority certificates - # ca_chain.pem: >- - # ... - # # Client Certificate and Private Key - # client.pem: >- - # ... - #base64Files: - # # Certificate Authority certificates - # trust.jks: >- - # ... - # # Client Certificate and Private Key - # client.jks: >- - # ... - -secrets: - application.conf: | - # Secret will be used to sign session cookies, CSRF tokens and for other encryption utilities. - # It is highly recommended to change this value before running cerebro in production. - secret = "ki:s:[[@=Ag?QI`W2jMwkY:eqvrJ]JqoJyi2axj3ZvOv^/KavOT4ViJSv?6YY4[N" - - # Application base path - basePath = "/" - - # Defaults to RUNNING_PID at the root directory of the app. - # To avoid creating a PID file set this value to /dev/null - #pidfile.path = "/var/run/cerebro.pid" - pidfile.path=/dev/null - - # Rest request history max size per user - rest.history.size = 50 // defaults to 50 if not specified - - # Path of local database file - #data.path: "/var/lib/cerebro/cerebro.db" - data.path = "./cerebro.db" - - # Authentication - auth = { - # Example of LDAP authentication - #type: ldap - #settings: { - #url = "ldap://host:port" - #base-dn = "ou=active,ou=Employee" - #method = "simple" - #user-domain = "domain.com" - #} - # Example of simple username/password authentication - #type: basic - #settings: { - #username = "admin" - #password = "1234" +## =============== MAIN Component =============== +main: + enabled: true + + replicas: 1 + + heapSize: "128m" + +# ------- Configuration follows for containerName: cerebro ------- + image: + repository: quay.io/centerforopenscience/cerebro + tag: master + pullPolicy: Always + + containerName: cerebro + + env: + - name: JAVA_OPTS + value: "-Djava.net.preferIPv4Stack=true -Xms{{ .Values.main.heapSize }} -Xmx{{ .Values.main.heapSize }}" + + ports: + - name: http + containerPort: 9000 + protocol: TCP + + volumeMounts: + - name: secret + mountPath: /opt/cerebro/conf/application.conf + subPath: application.conf + ## If TLS enabled + # - name: secret + # mountPath: /certs/ca_chain.pem + # subPath: certs-ca_chain.pem + # readOnly: true + # - name: secret + # mountPath: /certs/client.pem + # subPath: certs-client.pem + # readOnly: true + # - name: secret + # mountPath: /certs/client.jks + # subPath: certs-client.jks + # readOnly: true + # - name: secret + # mountPath: /certs/trust.jks + # subPath: certs-trust.jks + # readOnly: true + + resources: + limits: + cpu: "1" + memory: "512Mi" + requests: + cpu: "25m" + memory: "256Mi" + + +# ------- Volumes configuration for the pod ------- + volumes: + - name: secret + secret: + secretName: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + + +# ------- Affitnity configuration ------- + affinity: {} + additionalAffinities: [] + + +# ------- Pod Annotations ------- + podAnnotations: + checksum/secret: '{{ include "cos-common.componentChecksum" (dict "root" . "name" "" "values" .Values.main "resource" "secret") }}' + + +# ------- Secrets configuration ------- +# Secret name: '{{ include "cos-common.fullname" (dict "root" . "name" "") | trim }}' + secret: + enabled: true + includeTls: "{{ .Values.tls.enabled }}" + data: + application.conf: | + # Secret will be used to sign session cookies, CSRF tokens and for other encryption utilities. + # It is highly recommended to change this value before running cerebro in production. + secret = "ki:s:[[@=Ag?QI`W2jMwkY:eqvrJ]JqoJyi2axj3ZvOv^/KavOT4ViJSv?6YY4[N" + + # Application base path + basePath = "/" + + # Defaults to RUNNING_PID at the root directory of the app. + # To avoid creating a PID file set this value to /dev/null + #pidfile.path = "/var/run/cerebro.pid" + pidfile.path=/dev/null + + # Rest request history max size per user + rest.history.size = 50 // defaults to 50 if not specified + + # Path of local database file + #data.path: "/var/lib/cerebro/cerebro.db" + data.path = "./cerebro.db" + + # Authentication + auth = { + # Example of LDAP authentication + #type: ldap + #settings: { + #url = "ldap://host:port" + #base-dn = "ou=active,ou=Employee" + #method = "simple" + #user-domain = "domain.com" + #} + # Example of simple username/password authentication + #type: basic + #settings: { + #username = "admin" + #password = "1234" + #} + } + + # A list of known hosts + hosts = [ + #{ + # host = "http://localhost:9200" + # name = "Some Cluster" + #}, + # Example of host with authentication + #{ + # host = "http://some-authenticated-host:9200" + # name = "Secured Cluster" + # auth = { + # username = "username" + # password = "secret-password" + # } + #} + ] + + #play.ws.ssl { + # #loose = { + # # disableHostnameVerification: true + # #} + # keyManager = { + # stores = [ + # { path: /certs/client.jks, password: "123456" } + # ] + # } + # trustManager = { + # stores = [ + # { path: /certs/trust.jks } + # ] + # } #} - } - - # A list of known hosts - hosts = [ - #{ - # host = "http://localhost:9200" - # name = "Some Cluster" - #}, - # Example of host with authentication - #{ - # host = "http://some-authenticated-host:9200" - # name = "Secured Cluster" - # auth = { - # username = "username" - # password = "secret-password" - # } - #} - ] - - #play.ws.ssl { - # #loose = { - # # disableHostnameVerification: true - # #} - # keyManager = { - # stores = [ - # { path: /certs/client.jks, password: "123456" } - # ] - # } - # trustManager = { - # stores = [ - # { path: /certs/trust.jks } - # ] - # } - #} + ## If TLS enabled + # certs-ca_chain.pem: "" + # certs-client.pem: "" + # base64Files: + # certs-client.jks: "" + # certs-trust.jks: "" + + +# ------- Selectors and etc. ------- + nodeSelector: {} + tolerations: [] diff --git a/cos-common/README.md b/cos-common/README.md index 3d2b535a..e1461596 100644 --- a/cos-common/README.md +++ b/cos-common/README.md @@ -64,7 +64,7 @@ Use `cos-common.statefulset`, `cos-common.job`, or `cos-common.cronjob` the same - `cos-common.pvc`: PersistentVolumeClaims when `persistence.enabled` (component-level) or `volumes[].persistence.enabled`; auto-skips `existingClaim`; supports size/class/selector and spec overrides. - `cos-common.networkpolicy`: NetworkPolicy defaulting to namespace-local ingress allow; optional egress and extra ingress/egress rules; supports additional named policies. - `cos-common.configmap`: Main ConfigMap (tpl-aware) plus `additionalConfigMaps[]`. -- `cos-common.secret`: Main Secret (auto base64, optional tls merge) plus `additionalSecrets[]`. +- `cos-common.secret`: Main Secret (auto base64, optional tls merge, accepts pre-encoded `base64Files`) plus `additionalSecrets[]`. - `cos-common.certificate`: cert-manager Certificate from `certificate` block plus `additionalCertificates[]`. - Helpers: `cos-common.componentChecksum` to hash rendered resources; label/name helpers; pod spec builder for containers/init/sidecars/volumes, etc. diff --git a/cos-common/templates/_helpers.tpl b/cos-common/templates/_helpers.tpl index 120bafe4..c768a29e 100644 --- a/cos-common/templates/_helpers.tpl +++ b/cos-common/templates/_helpers.tpl @@ -176,7 +176,7 @@ Compose image reference supporting tag or digest. {{- else -}} {{- printf "%s:latest" $repo -}} {{ end }} -{{ end }} +{{- end -}} {{/* Render a generic metadata block, merging labels and annotations. diff --git a/cos-common/templates/_secret.tpl b/cos-common/templates/_secret.tpl index 7b076413..47123582 100644 --- a/cos-common/templates/_secret.tpl +++ b/cos-common/templates/_secret.tpl @@ -22,6 +22,14 @@ {{- /* Global TLS configuration (shared across components). */ -}} {{- $tls := default dict $root.Values.tls -}} +{{- /* Pre-encoded entries (e.g., base64Files) can be dropped straight into data. */ -}} +{{- $encoded := dict }} +{{- with $src.base64Files }} + {{- range $key, $value := . }} + {{- $_ := set $encoded $key (nospace (tpl (toString $value) $root)) }} + {{- end }} +{{- end }} + {{/* ============================================================================ TLS MERGE Optionally inject TLS cert files into this Secret. @@ -46,7 +54,7 @@ {{- range $key, $value := . }} {{- $_ := set $data (printf "certs-%s-%s" $app $key) - (b64enc $value) + $value }} {{- end }} {{- end }} @@ -54,7 +62,7 @@ {{- /* Already-base64 files → normalize only. */ -}} {{- with $tlsCfg.base64Files }} {{- range $key, $value := . }} - {{- $_ := set $data + {{- $_ := set $encoded (printf "certs-%s-%s" $app $key) (nospace $value) }} @@ -74,8 +82,6 @@ - Non-strings are converted to string and encoded - `.stringData` is NOT encoded here ============================================================================ */}} -{{- $encoded := dict }} - {{- range $k, $v := $data }} {{- if kindIs "string" $v }} {{- /* Allow Helm templating inside secret values. */ -}} diff --git a/cos-common/values.schema.json b/cos-common/values.schema.json index 4b241d4c..f5da9df7 100644 --- a/cos-common/values.schema.json +++ b/cos-common/values.schema.json @@ -655,6 +655,12 @@ "type": "object", "additionalProperties": true }, + "base64Files": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "stringData": { "type": "object", "additionalProperties": true @@ -1053,6 +1059,12 @@ "type": "object", "additionalProperties": true }, + "base64Files": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "stringData": { "type": "object", "additionalProperties": true From c8e447a2c7636df5802489de032cabeb386aede9 Mon Sep 17 00:00:00 2001 From: Oleh Avramenko Date: Tue, 6 Jan 2026 12:07:05 +0200 Subject: [PATCH 11/11] Small update to defaults for certificate --- angular-osf/values.yaml | 5 ++--- mfr/values.yaml | 4 ++-- osf-cas/values.yaml | 6 +++--- osf-graveyvalet/values.yaml | 5 ++--- wb/values.yaml | 6 +++--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/angular-osf/values.yaml b/angular-osf/values.yaml index 5e40c305..1709b33b 100644 --- a/angular-osf/values.yaml +++ b/angular-osf/values.yaml @@ -234,11 +234,10 @@ main: dnsNames: - example.org acmeConfig: - http01: - ingress: 'example' + http01: {} + # ingress: '' domains: - example.org - - subdomain.example.org # additionalCertificates: # # cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "certificate") | trim }}' + name diff --git a/mfr/values.yaml b/mfr/values.yaml index 6ad4d03a..9410186f 100644 --- a/mfr/values.yaml +++ b/mfr/values.yaml @@ -299,8 +299,8 @@ main: dnsNames: - example.org acmeConfig: - http01: - ingress: 'example' + http01: {} + # ingress: '' domains: - example.org diff --git a/osf-cas/values.yaml b/osf-cas/values.yaml index b49259c4..e7661037 100644 --- a/osf-cas/values.yaml +++ b/osf-cas/values.yaml @@ -324,7 +324,7 @@ main: # ------- Certificate configuration ------- (if we want to create Certificate object) -# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' +# certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' certificate: enabled: false # secretName: secret-with-cert # default secret name is certificate name @@ -335,8 +335,8 @@ main: dnsNames: - example.org acmeConfig: - http01: - ingress: 'test' + http01: {} + # ingress: '' domains: - example.org diff --git a/osf-graveyvalet/values.yaml b/osf-graveyvalet/values.yaml index 2b474c24..d922418c 100644 --- a/osf-graveyvalet/values.yaml +++ b/osf-graveyvalet/values.yaml @@ -317,11 +317,10 @@ main: dnsNames: - example.org acmeConfig: - http01: - ingress: 'example' + http01: {} + # ingress: '' domains: - example.org - - subdomain.example.org # additionalCertificates: # # certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' + name diff --git a/wb/values.yaml b/wb/values.yaml index 44af6af7..e73e7068 100644 --- a/wb/values.yaml +++ b/wb/values.yaml @@ -348,7 +348,7 @@ main: # ------- Certificate configuration ------- (if we want to create Certificate object) -# cert name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' +# certificate name: '{{ include "cos-common.fullname" (dict "root" . "name" "cert") | trim }}' certificate: enabled: false # secretName: secret-with-cert # default secret name is certificate name @@ -359,8 +359,8 @@ main: dnsNames: - example.org acmeConfig: - http01: - ingress: 'test' + http01: {} + # ingress: '' domains: - example.org