diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 391a65b..8a373ba 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -5,7 +5,7 @@ on: - v* jobs: - test: + push_images: name: Test and Push Images runs-on: ubuntu-latest strategy: @@ -39,7 +39,7 @@ jobs: git-ref: "${{ github.ref }}" nginx-tag: "${{ steps.target_nginx_tag.outputs.tag }}" matrix-nginx: "${{ matrix.nginx }}" - docker-repository: "${{ secrets.DOCKER_REPOSITORY }}" + docker-repository: "${{ vars.DOCKER_REPOSITORY }}" - uses: docker/setup-qemu-action@v2 name: Set up QEMU - uses: docker/setup-buildx-action@v2 diff --git a/.github/workflows/publish-helm-chart.yaml b/.github/workflows/publish-helm-chart.yaml new file mode 100644 index 0000000..81d6857 --- /dev/null +++ b/.github/workflows/publish-helm-chart.yaml @@ -0,0 +1,44 @@ +name: Test and Push Helm Chart + +on: + push: + tags: + - 'v*' + +jobs: + test-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get Latest Semantic Version + id: get_latest_version + uses: PSanetra/git-semver-actions/latest@v1 + + - name: Set up Helm + uses: azure/setup-helm@v3 + + - name: Install Helm Unittest Plugin + run: helm plugin install https://github.com/helm-unittest/helm-unittest + + - name: Run Helm Lint + run: helm lint chart --strict + + - name: Run Helm Unit Tests + run: helm unittest chart --strict + + - name: Package Helm Chart + run: helm package chart -u --version ${{ steps.get_latest_version.outputs.version }} --destination . + + - name: Install yq + run: snap install yq + + - name: Login to Docker Hub + run: echo '${{ secrets.DOCKER_PASSWORD }}' | helm registry login ${{ vars.DOCKER_REPOSITORY }} --username '${{ secrets.DOCKER_USERNAME }}' --password-stdin + + - name: Push Helm Chart to OCI Registry + run: | + helm push $(yq -r '.name' ./chart/Chart.yaml | tr -d '\n')-${{ steps.get_latest_version.outputs.version }}.tgz oci://${{ vars.DOCKER_REPOSITORY }} + helm push $(yq -r '.name' ./chart/Chart.yaml | tr -d '\n')-${{ steps.get_latest_version.outputs.version }}.tgz oci://${{ vars.DOCKER_REPOSITORY }}:latest diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index 25b3e3e..ba181f9 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -33,12 +33,12 @@ jobs: id: target_image_name run: | IMAGE_TAG="${{ matrix.spa_server_major_version }}-nginx-${{ steps.target_nginx_tag.outputs.tag }}" - IMAGE_NAME="${{ secrets.DOCKER_REPOSITORY }}:${IMAGE_TAG}" + IMAGE_NAME="${{ vars.DOCKER_REPOSITORY }}:${IMAGE_TAG}" echo "::set-output name=image_name::${IMAGE_NAME}" echo "IMAGE_NAME=${IMAGE_NAME}" - TAGS="${{ secrets.DOCKER_REPOSITORY }}:${IMAGE_TAG}" + TAGS="${{ vars.DOCKER_REPOSITORY }}:${IMAGE_TAG}" if [ "latest" = "${{ matrix.spa_server_major_version }}" ] && [ "stable" = "${{ matrix.nginx }}" ]; then - TAGS="${TAGS},${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.spa_server_major_version }},${{ secrets.DOCKER_REPOSITORY }}:latest" + TAGS="${TAGS},${{ vars.DOCKER_REPOSITORY }}:${{ matrix.spa_server_major_version }},${{ vars.DOCKER_REPOSITORY }}:latest" fi echo "::set-output name=tags::${TAGS}" echo "TAGS=${TAGS}" diff --git a/README.md b/README.md index e729e5b..3be6a10 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # Single Page Application Server + ![Update Docker Images](https://github.com/codecentric/single-page-application-server/workflows/Update%20Docker%20Images/badge.svg) -This image can be used as a base image for single page applications. It is itself based on Nginx. +This container image provides a base for serving Single Page Applications (SPAs), leveraging Nginx as its web server. + +For a fitting Helm Chart see [chart/README.md](https://github.com/codecentric/single-page-application-server/blob/master/chart/README.md). ## Tags -The following tags will be updated automatically with the latest nginx base image on a weekly basis: +The following tags are updated automatically on a weekly basis with the latest Nginx base image: * `latest` (alias for `1-nginx-stable-alpine`) * `1` (alias for `1-nginx-stable-alpine`) @@ -14,7 +17,7 @@ The following tags will be updated automatically with the latest nginx base imag * `latest-nginx-mainline-alpine` (alias for `1-nginx-mainline-alpine`) * `1-nginx-mainline-alpine` -There will also be tags for specific versions. +Additional tags for specific Nginx versions are also available. ## Examples @@ -22,46 +25,65 @@ Examples for usage with Angular and React are located in the `examples` director ## General Features -* Any non-existing routes return the root `index.html` - * Does NOT apply to resource routes with filename extensions such as: `js | css | ico | pdf | flv | jpg | jpeg | png | gif | swf` -* Configure application dynamically at container startup -* Configure base element -* Support environment specific configuration depending on the requested host (port and domain) -* Hashed resources are cached indefinitely by the browser without revalidating - * Resource name needs to look like `my-script.3f8a240b.js` or `my-script.3f8a240b.chunk.js` - * Hash needs to consist of at least 8 characters - * Applies to resources with filename extensions such as: `js | css | ico | pdf | flv | jpg | jpeg | png | gif | swf` -* HTTP 2 is enabled by default for HTTPS connections +- **SPA Routes Handling**: Routes not matching static files will serve `index.html`, with exceptions for resources like `.js`, `.css`, etc. +- **Dynamic Configuration**: Configure applications at container startup. +- **Environment-Specific Config**: Customize settings based on port and domain. +- **Resource Caching**: Hashed resources are cached indefinitely. Resources must include a hash of at least 8 characters. +- **HTTP/2**: Enabled by default for HTTPS connections. +- **Helm Chart**: A general [Helm chart](https://github.com/codecentric/single-page-application-server/blob/master/chart/README.md) is available for applications using this image. ## Security Features -* Restrictive Content Security Policy by default - * Server API endpoints can be whitelisted automatically -* Referrer is disabled by default -* Content Type Sniffing is disabled by default -* HTTPS is enforced via HSTS by default if enabled -* HTTPS uses [recommended OWASP protocols and cipher suites](https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html) -* Container runs as non-root user - * Nevertheless the server can still bind to port 80 and 443 -* Serving source map files (*.js.map, *.css.map) is disabled by default +- **Content Security Policy**: Restrictive by default, with automatic whitelisting for server API endpoints. +- **Referrer Policy**: Disabled by default to prevent leakage. +- **Content Type Sniffing**: Disabled by default. +- **HTTPS**: Enforced via HSTS if enabled; uses [recommended OWASP protocols and cipher suites.](https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html) +- **Non-Root User**: The container runs as a non-root user but can bind to ports 80 and 443. +- **Source Maps**: Disabled by default. +- **Read-only root filesystem**: [Supported at container runtime](#read-only-root-filesystem-support) ## Configuration ### App Directory -Copy your SPA resources to `/app/`. All resources in this directory will be served by the Nginx server. +Place your SPA resources in `/app/`. All files in this directory will be served by Nginx. ### YAML Configuration -The application container is configured via YAML files at startup time. +Configure the application through YAML files at startup: -If you need to configure some default settings for your application image, you can add a default configuration file to `/config/default.yaml`. +1. **Default Configuration**: Add a default configuration file to `/config/default.yaml`. Usually added during `docker build`. +2. **Runtime Configuration**: Mount a runtime configuration file at `/config/config.yaml`. This file will override default settings. -To configure settings at runtime, you can mount your runtime configuration file at `/config/config.yaml`. Every specified setting in this file will override the default setting. +#### Example Configuration -It is also possible to merge multiple configuration files by specifying the `CONFIG_FILES` environment variable like `CONFIG_FILES="file:///config/config1.yaml|file:///config/config2.yaml"`. The configuration options, specified in the first configuration file in that variable, will have priority over the options in later declared files. +```yaml +default: + spa_config: + appTitle: "My Application" + endpoints: + api: "https://api.example.com" +``` + +You can also define host-specific configurations: + +```yaml +default: + spa_config: + appTitle: "My Default Title" + endpoints: + api: "https://api.example.com" +special_host: + server_names: + - "special.example.com" + spa_config: + appTitle: "My Domain-specific Title" +``` + +#### Configuration Reference The following configuration shows the default values of this base image for every available setting: + ```yaml default: # Specifies to which host names this configuration should apply. @@ -132,40 +154,20 @@ default: style-src: "'self'" ``` -Aside to the `default` configuration block, you can also define other blocks, which might define configuration settings for special hosts. The non-default blocks will inherit the configured settings of the default-block if they are not explicitly redeclared. - -Example: -```yaml -default: - spa_config: - appTitle: "My Default Application" - endpoints: - globalApi: "https://api.example.com" -special_host: - server_names: - - "special.example.com" - spa_config: - appTitle: "My Special Application" -``` - -With this configuration the application would have the app title "My Special Application", when it is accessed via the host `special.example.com`, while the endpoints would stay the same in every instance of the application. - ## Read-only Root Filesystem Support -It is recommended to use a read-only root filesystem when running containers. However, the following directories must remain writable when using this base image: +For security, use a read-only root filesystem. Ensure the following directories are writable: -* `/config/.out` - * This base image generates files in this directory at startup. -* `/tmp` - * Nginx uses this directory to manage cached files and the nginx.pid file. For more information, see [nginxinc/docker-nginx-unprivileged#troubleshooting-tips](https://github.com/nginxinc/docker-nginx-unprivileged/tree/af6e325d35e6833af9cdda8493866b88649e8aaf?tab=readme-ov-file#troubleshooting-tips). +* `/config/.out`: Used for file generation. +* `/tmp`: Used by Nginx for cached files and `nginx.pid`. -It is possible to mount these directories as writable volumes. When using Kubernetes, one solution is to mount `emptyDir` volumes at these mount points. +When using Kubernetes, consider mounting these directories as writable volumes with `emptyDir`. ## Development -Configuration files are dynamically generated via [gomplate templates](https://docs.gomplate.ca/). - -Tests are written in Java using [Testcontainers](https://www.testcontainers.org/). +* **Configuration Generation**: Uses [gomplate templates](https://docs.gomplate.ca/). +* **Image Tests**: Written in Java using [Testcontainers](https://www.testcontainers.org/). +* **Helm Chart Tests**: Uses the [helm-unittest](https://github.com/helm-unittest/helm-unittest) Helm plugin. ## License diff --git a/chart/.helmignore b/chart/.helmignore new file mode 100644 index 0000000..dea823c --- /dev/null +++ b/chart/.helmignore @@ -0,0 +1,26 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Testing +ci +tests diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..d4c52ab --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: single-page-application-server +description: A Helm chart for deployment of applications which are using the codecentric/single-page-application-server base image. +type: application +version: 0.0.0 diff --git a/chart/README.md b/chart/README.md new file mode 100644 index 0000000..e9307db --- /dev/null +++ b/chart/README.md @@ -0,0 +1,144 @@ +# Helm Chart for Single Page Application Server + +## Overview + +This Helm chart deploys a Single Page Application (SPA) Server based on Nginx. It provides a scalable and secure way to serve your SPA with customizable configuration options. + +## Installation + +You can install the chart with the release name `my-release` using the Docker Hub repository. + +### Using the `latest` Tag + +To install the chart using the `latest` version from Docker Hub: + +```sh +helm install my-release oci://docker.io/codecentric/single-page-application-server --version latest +``` + +### Using a Specific Version (`{MAJOR}.{MINOR}.{PATCH}`) Tag + +To install a specific version of the Helm chart from Docker Hub, replace `{MAJOR}.{MINOR}.{PATCH}` with the desired version number of this chart: + +```sh +helm install my-release oci://docker.io/codecentric/single-page-application-server \ + --version {MAJOR}.{MINOR}.{PATCH} \ + --set pod.container.image.repository=my-docker-repository/my-app-image \ + --set pod.container.image.tag=latest +``` + +For example, to install version `1.2.3`, you would use: + +```sh +helm install my-release oci://docker.io/codecentric/single-page-application-server \ + --version 1.2.3 \ + --set pod.container.image.repository=my-docker-repository/my-app-image \ + --set pod.container.image.tag=latest +``` + +## Values + +The following table lists the configurable parameters of the Helm chart and their default values. + +| Parameter | Description | Default | +|--------------------------------------------------------| ----------------------------------------------------------------------- | -------------------------------- | +| `nameOverride` | Explicit name of the application (not the application instance) | `""` | +| `fullnameOverride` | Explicit fully qualified name of the application (not the application instance) | `""` | +| `config.default.base_href` | Base href for the SPA | `"/"` | +| `config.default.spa_config.endpoints` | SPA endpoints configuration | `{}` | +| `config.default.http.enabled` | Enable HTTP | `true` | +| `config.default.http.port` | HTTP port | `80` | +| `config.default.https.enabled` | Enable HTTPS | `false` | +| `config.default.https.port` | HTTPS port | `443` | +| `config.default.https.ssl_certificate` | Path to SSL certificate | `/var/run/secrets/tls/tls.crt` | +| `config.default.https.ssl_certificate_key` | Path to SSL certificate key | `/var/run/secrets/tls/tls.key` | +| `labels` | Labels added to all resource types | `{"app.kubernetes.io/component": "frontend"}` | +| `pod.labels` | Labels for the pod | `{}` | +| `pod.annotations` | Annotations for the pod | `{}` | +| `pod.replicaCount` | Number of replicas | `2` | +| `pod.securityContext.runAsNonRoot` | Run containers as non-root | `true` | +| `pod.volumes` | Additional volumes for the pod | `[]` | +| `pod.chartVolumes.configOut.medium` | Medium for config output volume | `"Memory"` | +| `pod.chartVolumes.configOut.sizeLimit` | Size limit for config output volume | `"256Mi"` | +| `pod.chartVolumes.tmp.medium` | Medium for tmp volume | `"Memory"` | +| `pod.chartVolumes.tmp.sizeLimit` | Size limit for tmp volume | `"2Gi"` | +| `pod.container.image.repository` | Container image repository | `""` | +| `pod.container.image.pullPolicy` | Container image pull policy | `Always` | +| `pod.container.image.tag` | Container image tag | `""` | +| `pod.container.image.pullSecrets` | Image pull secrets | `[]` | +| `pod.container.securityContext.capabilities.drop` | Capabilities to drop | `["ALL"]` | +| `pod.container.securityContext.readOnlyRootFilesystem` | Use read-only root filesystem | `true` | +| `pod.container.securityContext.runAsNonRoot` | Run container as non-root | `true` | +| `pod.container.livenessProbe.httpGet.path` | Path for liveness probe | `/health/liveness` | +| `pod.container.livenessProbe.httpGet.port` | Port for liveness probe | `http` | +| `pod.container.readinessProbe.httpGet.path` | Path for readiness probe | `/health/readiness` | +| `pod.container.readinessProbe.httpGet.port` | Port for readiness probe | `http` | +| `pod.container.volumeMounts` | Volume mounts for the container | `[]` | +| `pod.container.resources.limits.cpu` | CPU limit for the container | `2` | +| `pod.container.resources.limits.memory` | Memory limit for the container | `4Gi` | +| `pod.container.resources.requests.cpu` | CPU request for the container | `100m` | +| `pod.container.resources.requests.memory` | Memory request for the container | `512Mi` | +| `pod.nodeSelector` | Node selector for the pod | `{}` | +| `pod.tolerations` | Tolerations for the pod | `[]` | +| `pod.affinity` | Affinity for the pod | `{}` | +| `pod.disruptionBudget.enabled` | Enable Pod Disruption Budget | `true` | +| `pod.disruptionBudget.maxUnavailable` | Maximum unavailable pods | `1` | +| `pod.disruptionBudget.minAvailable` | Minimum available pods | `0` | +| `pod.autoscaling.enabled` | Enable autoscaling | `false` | +| `pod.autoscaling.minReplicas` | Minimum replicas for autoscaling | `1` | +| `pod.autoscaling.maxReplicas` | Maximum replicas for autoscaling | `4` | +| `pod.autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization percentage for autoscaling | `50` | +| `pod.autoscaling.customMetrics` | Custom metrics for autoscaling | `[]` | +| `service.type` | Type of the service | `ClusterIP` | +| `service.httpPort` | HTTP port for the service | `80` | +| `service.httpNodePort` | HTTP NodePort for the service | `""` | +| `service.httpsPort` | HTTPS port for the service | `443` | +| `service.httpsNodePort` | HTTPS NodePort for the service | `""` | +| `service.annotations` | Annotations for the service | `{}` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `true` | +| `networkPolicy.policyTypes` | Types of network policies | `["Egress"]` | +| `networkPolicy.egress` | Egress rules for the NetworkPolicy | `[]` | +| `networkPolicy.ingress` | Ingress rules for the NetworkPolicy | `[]` | +| `http.hosts` | HTTP hosts configuration | `{}` | +| `ingress.enabled` | Enable Ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.targetServicePortName` | Target service port name for Ingress | `http` | +| `ingress.annotations` | Annotations for Ingress | `{}` | +| `openshift.route.enabled` | Enable OpenShift Route | `false` | +| `openshift.route.annotations` | Annotations for OpenShift Route | `{}` | +| `openshift.route.tls.termination` | TLS termination for OpenShift Route | `reencrypt` | +| `openshift.route.tls.insecureEdgeTerminationPolicy` | Insecure edge termination policy for OpenShift Route | `Redirect` | + +## Usage + +### Runtime Configuration + +The application is configured using runtime configurations specified in `config`. Default values can be overridden by specifying custom values. + +### Labels and Annotations + +Labels and annotations can be added to various resource types such as pods and services. These can be specified under the `labels` and `annotations` sections. + +### Pod Configuration + +The pod configuration allows setting various parameters such as the number of replicas, security context, resource requests, and limits. Additional volumes and volume mounts can also be specified. + +### Service Configuration + +Service configuration includes setting the type of service (e.g., `ClusterIP`), ports, and annotations. + +### Network Policies + +Network policies can be configured to control the ingress and egress traffic to the application. + +### Ingress and OpenShift Route + +Ingress and OpenShift Route configurations are provided to expose the application externally. + +### Autoscaling + +Autoscaling can be enabled to automatically adjust the number of replicas based on CPU utilization or custom metrics. + +## License + +MIT diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..8d39fb7 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* +Expand the name of the Release. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "single-page-application-server.name" -}} +{{- .Values.nameOverride | default .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Fully qualified application name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "single-page-application-server.fullname" -}} +{{- .Values.fullnameOverride | default .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "single-page-application-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "single-page-application-server.labels" -}} +helm.sh/chart: {{ include "single-page-application-server.chart" . }} +{{ include "single-page-application-server.selectorLabels" . }} +app.kubernetes.io/version: {{ ( "" | or (index .Values.labels "app.kubernetes.io/version")) | default (.Values.pod.container.image.tag | required "image.tag is required!") | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{ toYaml .Values.labels }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "single-page-application-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "single-page-application-server.name" . | quote }} +{{- if (eq (include "single-page-application-server.name" . ) .Release.Name) }} +app.kubernetes.io/instance: "default" +{{- else }} +app.kubernetes.io/instance: {{ .Release.Name | quote }} +{{- end}} +{{- end }} diff --git a/chart/templates/config.yaml b/chart/templates/config.yaml new file mode 100644 index 0000000..d3d7779 --- /dev/null +++ b/chart/templates/config.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "single-page-application-server.fullname" . }} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} +data: + config.yaml: | + {{- toYaml .Values.config | nindent 4 }} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml new file mode 100644 index 0000000..5c3bff6 --- /dev/null +++ b/chart/templates/deployment.yaml @@ -0,0 +1,113 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "single-page-application-server.fullname" . }} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} +spec: + {{- if not .Values.pod.autoscaling.enabled }} + replicas: {{ .Values.pod.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "single-page-application-server.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + "checksum/config-map": {{ $.Values.config | toYaml | sha1sum | quote }} + {{- with .Values.pod.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "single-page-application-server.labels" . | nindent 8 }} + {{- with .Values.pod.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.pod.container.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: false + securityContext: + {{- toYaml .Values.pod.securityContext | nindent 8 }} + containers: + - name: single-page-application + securityContext: + {{- toYaml .Values.pod.container.securityContext | nindent 12 }} + image: "{{ .Values.pod.container.image.repository | required "image.repository is required!" }}:{{ .Values.pod.container.image.tag | required "image.tag is required!" }}" + imagePullPolicy: {{ .Values.pod.container.image.pullPolicy }} + ports: + {{- if .Values.config.default.http.enabled }} + - name: http + containerPort: {{ .Values.config.default.http.port }} + protocol: TCP + {{- end }} + {{- if .Values.config.default.https.enabled }} + - name: https + containerPort: {{ .Values.config.default.https.port }} + protocol: TCP + {{- end }} + resources: + {{- toYaml .Values.pod.container.resources | nindent 12 }} + livenessProbe: + {{- toYaml .Values.pod.container.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.pod.container.readinessProbe | nindent 12 }} + + volumeMounts: + - name: "config" + mountPath: "/config/config.yaml" + subPath: config.yaml + readOnly: true + - name: "config-out" + mountPath: "/config/.out" + readOnly: false + - name: "tmp" + mountPath: "/tmp" + readOnly: false + {{- if (and .Values.openshift.route.enabled (eq .Values.openshift.route.tls.termination "reencrypt")) }} + - name: "tls" + mountPath: /var/run/secrets/tls + readOnly: true + {{- end }} + {{- with .Values.pod.container.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + + volumes: + - name: config + configMap: + name: {{ include "single-page-application-server.fullname" . }} + optional: false + defaultMode: 0444 + - name: config-out + emptyDir: + medium: {{ .Values.pod.chartVolumes.configOut.medium | quote }} + sizeLimit: {{ .Values.pod.chartVolumes.configOut.sizeLimit | quote }} + - name: tmp + emptyDir: + medium: {{ .Values.pod.chartVolumes.tmp.medium | quote }} + sizeLimit: {{ .Values.pod.chartVolumes.tmp.sizeLimit | quote }} + {{- if (and .Values.openshift.route.enabled (eq .Values.openshift.route.tls.termination "reencrypt")) }} + - name: "tls" + secret: + secretName: "{{ include "single-page-application-server.fullname" . }}-cert" + defaultMode: 0444 + optional: false + {{- end }} + {{- with .Values.pod.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pod.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pod.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.pod.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/chart/templates/hpa.yaml b/chart/templates/hpa.yaml new file mode 100644 index 0000000..6f23993 --- /dev/null +++ b/chart/templates/hpa.yaml @@ -0,0 +1,27 @@ +{{- if .Values.pod.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "single-page-application-server.fullname" . }} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "single-page-application-server.fullname" . }} + minReplicas: {{ .Values.pod.autoscaling.minReplicas }} + maxReplicas: {{ .Values.pod.autoscaling.maxReplicas }} + metrics: + {{- if .Values.pod.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.pod.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.pod.autoscaling.customMetrics }} + {{- toYaml .Values.pod.autoscaling.customMetrics | nindent 4 }} + {{- end }} +{{- end }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..4acad64 --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,34 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "single-page-application-server.fullname" . | quote}} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className | required "ingress.className is required!" }} + rules: + {{- range $hostname, $hostConfig := (.Values.http.hosts | required "http.hosts is required if ingress is enabled!") }} + - host: {{ $hostname | quote }} + http: + paths: + - path: {{ $hostConfig.path | default $.Values.config.default.base_href | quote }} + pathType: "Prefix" + backend: + service: + name: {{ include "single-page-application-server.fullname" $ | quote}} + port: + name: {{ $.Values.ingress.targetServicePortName }} + {{- end }} + tls: {{- range $hostName, $hostConfig := .Values.http.hosts }} + {{- if $hostConfig.tlsSecretName }} + - hosts: + - {{ $hostName | quote }} + secretName: {{ $hostConfig.tlsSecretName }} + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/network-policy.yaml b/chart/templates/network-policy.yaml new file mode 100644 index 0000000..5e364a7 --- /dev/null +++ b/chart/templates/network-policy.yaml @@ -0,0 +1,18 @@ +{{- if .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "single-page-application-server.fullname" . | quote}} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "single-page-application-server.selectorLabels" . | nindent 6 }} + policyTypes: + {{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }} + ingress: + {{- toYaml .Values.networkPolicy.ingress | nindent 4 }} + egress: + {{- toYaml .Values.networkPolicy.egress | nindent 4 }} +{{- end -}} diff --git a/chart/templates/pdb.yaml b/chart/templates/pdb.yaml new file mode 100644 index 0000000..4d4e7bf --- /dev/null +++ b/chart/templates/pdb.yaml @@ -0,0 +1,21 @@ +{{- if .Values.pod.disruptionBudget.enabled -}} +{{- if ge .Values.pod.disruptionBudget.minAvailable .Values.pod.replicaCount -}} + {{- fail "replicaCount must be greater than pod.disruptionBudget.minAvailable!" -}} +{{- end -}} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "single-page-application-server.fullname" . }} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} +spec: + {{ if .Values.pod.disruptionBudget.minAvailable -}} + minAvailable: {{ .Values.pod.disruptionBudget.minAvailable }} + {{ end -}} + {{ if .Values.pod.disruptionBudget.maxUnavailable -}} + maxUnavailable: {{ .Values.pod.disruptionBudget.maxUnavailable }} + {{ end -}} + selector: + matchLabels: + {{- include "single-page-application-server.selectorLabels" . | nindent 6 }} +{{- end -}} diff --git a/chart/templates/route.yaml b/chart/templates/route.yaml new file mode 100644 index 0000000..37946b4 --- /dev/null +++ b/chart/templates/route.yaml @@ -0,0 +1,28 @@ +{{- if $.Values.openshift.route.enabled -}} +{{- range $hostname, $hostConfig := ($.Values.http.hosts | required "http.hosts is required if route is enabled!") }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "single-page-application-server.fullname" $ | trunc 22 | trimSuffix "-" }}-{{ sha1sum $hostname }} + labels: + spa-hostname: "{{- $hostname -}}" + {{- include "single-page-application-server.labels" $ | nindent 4 }} + {{- with $.Values.openshift.route.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + host: {{ $hostname }} + {{- if $hostConfig.path }} + path: {{ $hostConfig.path | quote }} + {{- end }} + to: + kind: Service + name: {{ include "single-page-application-server.fullname" $ | quote}} + port: + targetPort: {{ if (eq $.Values.openshift.route.tls.termination "reencrypt") -}}https{{- else -}}http{{- end}} + tls: + {{- toYaml $.Values.openshift.route.tls | nindent 4 }} +{{- end }} +{{- end }} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 0000000..876cccb --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "single-page-application-server.fullname" . }} + labels: + {{- include "single-page-application-server.labels" . | nindent 4 }} + annotations: + {{- if (and .Values.openshift.route.enabled (eq .Values.openshift.route.tls.termination "reencrypt")) }} + "service.beta.openshift.io/serving-cert-secret-name": "{{ include "single-page-application-server.fullname" . }}-cert" + {{- end }} + {{- with .Values.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.httpPort }} + targetPort: http + protocol: TCP + name: http + appProtocol: http + {{- if .Values.service.httpNodePort }} + nodePort: {{ .Values.service.httpNodePort }} + {{- end }} + - port: {{ .Values.service.httpsPort }} + targetPort: https + protocol: TCP + name: https + appProtocol: https + {{- if .Values.service.httpsNodePort }} + nodePort: {{ .Values.service.httpsNodePort }} + {{- end }} + selector: + {{- include "single-page-application-server.selectorLabels" . | nindent 4 }} diff --git a/chart/tests/__snapshot__/config_test.yaml.snap b/chart/tests/__snapshot__/config_test.yaml.snap new file mode 100644 index 0000000..7bb8674 --- /dev/null +++ b/chart/tests/__snapshot__/config_test.yaml.snap @@ -0,0 +1,27 @@ +should render minimal values: + 1: | + apiVersion: v1 + data: + config.yaml: | + default: + base_href: / + http: + enabled: true + port: 80 + https: + enabled: false + port: 443 + ssl_certificate: /var/run/secrets/tls/tls.crt + ssl_certificate_key: /var/run/secrets/tls/tls.key + spa_config: + endpoints: {} + kind: ConfigMap + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME diff --git a/chart/tests/__snapshot__/deployment_test.yaml.snap b/chart/tests/__snapshot__/deployment_test.yaml.snap new file mode 100644 index 0000000..f0002ab --- /dev/null +++ b/chart/tests/__snapshot__/deployment_test.yaml.snap @@ -0,0 +1,1005 @@ +should mount tls secret if openshift.route.enabled and openshift.route.tls.termination is reencrypt: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + - mountPath: /var/run/secrets/tls + name: tls + readOnly: true + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp + - name: tls + secret: + defaultMode: 292 + optional: false + secretName: RELEASE-NAME-cert +should not render replicas if autoscaling is enabled: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should render minimal values: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should render with affinity: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: disktype + operator: In + values: + - ssd + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should render with extra volume: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + - mountPath: /mnt/foo + name: myvolume + readOnly: true + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp + - name: myvolume + secret: + optional: false + secretName: mysecret +should render with nodeSelector: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + nodeSelector: + myNodeLabel: myNodeLabelValue + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should render with pull secret: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + imagePullSecrets: + - my-pull-secret + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should render with tolerations: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: d2cf2f3a678c322f5883feac881f8be66a90c8fd + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + tolerations: + - effect: NoSchedule + key: example-key + operator: Exists + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should support alternative http port: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: a65dc1dcf5388bced1711c8a1233763c4cfb3441 + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should support alternative https port: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: dce4af80cd48a768c53dfab8d2832b9a29701d5e + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 8443 + name: https + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp +should support enabling https: + 1: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + template: + metadata: + annotations: + checksum/config-map: 321726841793faccc874ccca587d3c1092e28a33 + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spec: + automountServiceAccountToken: false + containers: + - image: spa-example:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /health/liveness + port: http + name: single-page-application + ports: + - containerPort: 80 + name: http + protocol: TCP + - containerPort: 443 + name: https + protocol: TCP + readinessProbe: + httpGet: + path: /health/readiness + port: http + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /config/config.yaml + name: config + readOnly: true + subPath: config.yaml + - mountPath: /config/.out + name: config-out + readOnly: false + - mountPath: /tmp + name: tmp + readOnly: false + securityContext: + runAsNonRoot: true + volumes: + - configMap: + defaultMode: 292 + name: RELEASE-NAME + optional: false + name: config + - emptyDir: + medium: Memory + sizeLimit: 256Mi + name: config-out + - emptyDir: + medium: Memory + sizeLimit: 2Gi + name: tmp diff --git a/chart/tests/__snapshot__/hpa_test.yaml.snap b/chart/tests/__snapshot__/hpa_test.yaml.snap new file mode 100644 index 0000000..b44167c --- /dev/null +++ b/chart/tests/__snapshot__/hpa_test.yaml.snap @@ -0,0 +1,86 @@ +should not render cpu metrics if targetCPUUtilizationPercentage is set to 0: + 1: | + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + maxReplicas: 4 + metrics: null + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: RELEASE-NAME +should render hpa with custom metrics: + 1: | + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + maxReplicas: 4 + metrics: + - resource: + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: Resource + - object: + describedObject: + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: some-ingress + metric: + name: requests-per-second + target: + type: Value + value: 10k + type: Object + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: RELEASE-NAME +should render hpa with default values if enabled: + 1: | + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + maxReplicas: 4 + metrics: + - resource: + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: RELEASE-NAME diff --git a/chart/tests/__snapshot__/ingress_test.yaml.snap b/chart/tests/__snapshot__/ingress_test.yaml.snap new file mode 100644 index 0000000..039ddcd --- /dev/null +++ b/chart/tests/__snapshot__/ingress_test.yaml.snap @@ -0,0 +1,111 @@ +should render host with custom path and targetServicePortName: + 1: | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ingressClassName: testingress + rules: + - host: test.example.com + http: + paths: + - backend: + service: + name: RELEASE-NAME + port: + name: http + path: /custom-path + pathType: Prefix + tls: null +should render ingress with default values if enabled: + 1: | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ingressClassName: testingress + rules: + - host: test.example.com + http: + paths: + - backend: + service: + name: RELEASE-NAME + port: + name: http + path: / + pathType: Prefix + tls: null +should render ingress with default.base_href if path is not defined: + 1: | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ingressClassName: testingress + rules: + - host: test.example.com + http: + paths: + - backend: + service: + name: RELEASE-NAME + port: + name: http + path: /base-path + pathType: Prefix + tls: null +should render tls property if http."host".tlsSecretName is set: + 1: | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ingressClassName: testingress + rules: + - host: test.example.com + http: + paths: + - backend: + service: + name: RELEASE-NAME + port: + name: http + path: / + pathType: Prefix + tls: + - hosts: + - test.example.com + secretName: my-tls-secret diff --git a/chart/tests/__snapshot__/network-policy_test.yaml.snap b/chart/tests/__snapshot__/network-policy_test.yaml.snap new file mode 100644 index 0000000..3051491 --- /dev/null +++ b/chart/tests/__snapshot__/network-policy_test.yaml.snap @@ -0,0 +1,81 @@ +should render network-policy with custom egress policy: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + egress: + - ports: + - port: 5978 + protocol: TCP + to: + - ipBlock: + cidr: 10.0.0.0/24 + ingress: [] + podSelector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + policyTypes: + - Egress +should render network-policy with custom ingress policy: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + egress: [] + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: myingresscontroller + ports: + - port: http + protocol: TCP + - port: https + protocol: TCP + podSelector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + policyTypes: + - Ingress +should render network-policy with egress isolation by default: + 1: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + egress: [] + ingress: [] + podSelector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + policyTypes: + - Egress diff --git a/chart/tests/__snapshot__/pdb_test.yaml.snap b/chart/tests/__snapshot__/pdb_test.yaml.snap new file mode 100644 index 0000000..4eff837 --- /dev/null +++ b/chart/tests/__snapshot__/pdb_test.yaml.snap @@ -0,0 +1,38 @@ +should render pdb by default: + 1: | + apiVersion: policy/v1 + kind: PodDisruptionBudget + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME +should render with minAvailable: + 1: | + apiVersion: policy/v1 + kind: PodDisruptionBudget + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME diff --git a/chart/tests/__snapshot__/route_test.yaml.snap b/chart/tests/__snapshot__/route_test.yaml.snap new file mode 100644 index 0000000..9a87687 --- /dev/null +++ b/chart/tests/__snapshot__/route_test.yaml.snap @@ -0,0 +1,119 @@ +should render multiple routes with default values if enabled: + 1: | + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spa-hostname: test1.example.com + name: RELEASE-NAME-7c1ed4265ba3c1ce98e9cc6d503a6c5202c29ea5 + spec: + host: test1.example.com + port: + targetPort: https + tls: + insecureEdgeTerminationPolicy: Redirect + termination: reencrypt + to: + kind: Service + name: RELEASE-NAME + 2: | + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spa-hostname: test2.example.com + name: RELEASE-NAME-09e9022420541515ce5a1224d76c17598e617443 + spec: + host: test2.example.com + port: + targetPort: https + tls: + insecureEdgeTerminationPolicy: Redirect + termination: reencrypt + to: + kind: Service + name: RELEASE-NAME +should render route with default values if enabled: + 1: | + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spa-hostname: test.example.com + name: RELEASE-NAME-401f83bc96721eeeba6f5c1c54cf0a83dc08a30b + spec: + host: test.example.com + port: + targetPort: https + tls: + insecureEdgeTerminationPolicy: Redirect + termination: reencrypt + to: + kind: Service + name: RELEASE-NAME +should render targetPort with http if tls.termination is set to edge: + 1: | + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spa-hostname: test.example.com + name: RELEASE-NAME-401f83bc96721eeeba6f5c1c54cf0a83dc08a30b + spec: + host: test.example.com + port: + targetPort: http + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + to: + kind: Service + name: RELEASE-NAME +should support overriding tls.insecureEdgeTerminationPolicy: + 1: | + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + spa-hostname: test.example.com + name: RELEASE-NAME-401f83bc96721eeeba6f5c1c54cf0a83dc08a30b + spec: + host: test.example.com + port: + targetPort: https + tls: + insecureEdgeTerminationPolicy: None + termination: reencrypt + to: + kind: Service + name: RELEASE-NAME diff --git a/chart/tests/__snapshot__/service_test.yaml.snap b/chart/tests/__snapshot__/service_test.yaml.snap new file mode 100644 index 0000000..3fdcbef --- /dev/null +++ b/chart/tests/__snapshot__/service_test.yaml.snap @@ -0,0 +1,213 @@ +? should not set serving-cert-secret-name annotation if openshift.route is enabled, but openshift.route.tls.termination is not set to reencrypt +: 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: ClusterIP +should render minimal values: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: ClusterIP +? should set serving-cert-secret-name annotation if openshift.route is enabled and openshift.route.tls.termination is set to reencrypt +: 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: + service.beta.openshift.io/serving-cert-secret-name: RELEASE-NAME-cert + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: ClusterIP +should support alternative http port: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 8080 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: ClusterIP +should support alternative https port: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: ClusterIP +should support httpNodePort: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + nodePort: 8080 + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: NodePort +should support httpsNodePort: + 1: | + apiVersion: v1 + kind: Service + metadata: + annotations: null + labels: + app.kubernetes.io/component: frontend + app.kubernetes.io/instance: default + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: RELEASE-NAME + app.kubernetes.io/version: latest + helm.sh/chart: single-page-application-server-0.0.0 + name: RELEASE-NAME + spec: + ports: + - appProtocol: http + name: http + port: 80 + protocol: TCP + targetPort: http + - appProtocol: https + name: https + nodePort: 8443 + port: 443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/instance: default + app.kubernetes.io/name: RELEASE-NAME + type: NodePort diff --git a/chart/tests/config_test.yaml b/chart/tests/config_test.yaml new file mode 100644 index 0000000..8223d9c --- /dev/null +++ b/chart/tests/config_test.yaml @@ -0,0 +1,9 @@ +suite: test config +templates: + - config.yaml +tests: + - it: should render minimal values + values: + - ./values/minimal_values.yaml + asserts: + - matchSnapshot: {} diff --git a/chart/tests/deployment_test.yaml b/chart/tests/deployment_test.yaml new file mode 100644 index 0000000..e86cd1b --- /dev/null +++ b/chart/tests/deployment_test.yaml @@ -0,0 +1,124 @@ +suite: test deployment +templates: + - deployment.yaml +tests: + - it: should fail if image.tag is unset + set: + pod.container.image.repository: "some-repository" + asserts: + - failedTemplate: + errorMessage: "image.tag is required!" + - it: should fail if image.repository is unset + set: + pod.container.image.tag: "some-tag" + asserts: + - failedTemplate: + errorMessage: "image.repository is required!" + - it: should render minimal values + values: + - ./values/minimal_values.yaml + asserts: + - matchSnapshot: {} + - it: should support enabling https + values: + - ./values/minimal_values.yaml + set: + config.default.https.enabled: true + asserts: + - matchSnapshot: { } + - it: should support alternative https port + values: + - ./values/minimal_values.yaml + set: + config.default.https: + enabled: true + port: 8443 + asserts: + - matchSnapshot: { } + - it: should support alternative http port + values: + - ./values/minimal_values.yaml + set: + config.default.http: + port: 8080 + asserts: + - matchSnapshot: { } + - it: should render with pull secret + values: + - ./values/minimal_values.yaml + set: + pod.container.image.pullSecrets: + - my-pull-secret + asserts: + - matchSnapshot: { } + - it: should render with extra volume + values: + - ./values/minimal_values.yaml + set: + pod: + volumes: + - name: myvolume + secret: + secretName: mysecret + optional: false + container: + volumeMounts: + - name: myvolume + mountPath: "/mnt/foo" + readOnly: true + asserts: + - matchSnapshot: { } + - it: should render with nodeSelector + values: + - ./values/minimal_values.yaml + set: + pod: + nodeSelector: + myNodeLabel: myNodeLabelValue + asserts: + - matchSnapshot: { } + - it: should render with tolerations + values: + - ./values/minimal_values.yaml + set: + pod: + tolerations: + - key: "example-key" + operator: "Exists" + effect: "NoSchedule" + asserts: + - matchSnapshot: { } + - it: should render with affinity + values: + - ./values/minimal_values.yaml + set: + pod: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: disktype + operator: In + values: + - ssd + asserts: + - matchSnapshot: { } + - it: should mount tls secret if openshift.route.enabled and openshift.route.tls.termination is reencrypt + values: + - ./values/minimal_values.yaml + set: + openshift.route: + enabled: true + tls: + termination: reencrypt + asserts: + - matchSnapshot: { } + - it: should not render replicas if autoscaling is enabled + values: + - ./values/minimal_values.yaml + set: + pod.autoscaling: + enabled: true + asserts: + - matchSnapshot: { } diff --git a/chart/tests/hpa_test.yaml b/chart/tests/hpa_test.yaml new file mode 100644 index 0000000..46c52b1 --- /dev/null +++ b/chart/tests/hpa_test.yaml @@ -0,0 +1,44 @@ +suite: test hpa +templates: + - hpa.yaml +tests: + - it: should not render hpa by default + asserts: + - hasDocuments: + count: 0 + - it: should render hpa with default values if enabled + values: + - ./values/minimal_values.yaml + set: + pod.autoscaling.enabled: true + asserts: + - matchSnapshot: { } + - it: should render hpa with custom metrics + values: + - ./values/minimal_values.yaml + set: + pod.autoscaling: + enabled: true + customMetrics: + - type: Object + object: + metric: + name: requests-per-second + describedObject: + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: some-ingress + target: + type: Value + value: 10k + asserts: + - matchSnapshot: { } + - it: should not render cpu metrics if targetCPUUtilizationPercentage is set to 0 + values: + - ./values/minimal_values.yaml + set: + pod.autoscaling: + enabled: true + targetCPUUtilizationPercentage: 0 + asserts: + - matchSnapshot: { } \ No newline at end of file diff --git a/chart/tests/ingress_test.yaml b/chart/tests/ingress_test.yaml new file mode 100644 index 0000000..0389575 --- /dev/null +++ b/chart/tests/ingress_test.yaml @@ -0,0 +1,82 @@ +suite: test ingress +templates: + - ingress.yaml +tests: + - it: should not render ingress by default + asserts: + - hasDocuments: + count: 0 + - it: should fail to render if enabled but className is not defined + values: + - ./values/minimal_values.yaml + set: + ingress: + enabled: true + http: + hosts: + "test.example.com": {} + asserts: + - failedTemplate: + errorMessage: "ingress.className is required!" + - it: should fail to render if enabled but http.hosts are not defined + values: + - ./values/minimal_values.yaml + set: + ingress: + enabled: true + className: "testingress" + asserts: + - failedTemplate: + errorMessage: "http.hosts is required if ingress is enabled!" + - it: should render ingress with default values if enabled + values: + - ./values/minimal_values.yaml + set: + ingress: + enabled: true + className: "testingress" + http: + hosts: + "test.example.com": {} + asserts: + - matchSnapshot: { } + - it: should render ingress with default.base_href if path is not defined + values: + - ./values/minimal_values.yaml + set: + config.default.base_href: "/base-path" + ingress: + enabled: true + className: "testingress" + http: + hosts: + "test.example.com": { } + asserts: + - matchSnapshot: { } + - it: should render host with custom path and targetServicePortName + values: + - ./values/minimal_values.yaml + set: + ingress: + enabled: true + className: "testingress" + http: + hosts: + "test.example.com": + path: /custom-path + targetServicePortName: https + asserts: + - matchSnapshot: { } + - it: should render tls property if http."host".tlsSecretName is set + values: + - ./values/minimal_values.yaml + set: + ingress: + enabled: true + className: "testingress" + http: + hosts: + "test.example.com": + tlsSecretName: my-tls-secret + asserts: + - matchSnapshot: { } diff --git a/chart/tests/network-policy_test.yaml b/chart/tests/network-policy_test.yaml new file mode 100644 index 0000000..db83b07 --- /dev/null +++ b/chart/tests/network-policy_test.yaml @@ -0,0 +1,50 @@ +suite: test network-policy +templates: + - network-policy.yaml +tests: + - it: should not render network-policy if disabled + values: + - ./values/minimal_values.yaml + set: + networkPolicy.enabled: false + asserts: + - hasDocuments: + count: 0 + - it: should render network-policy with egress isolation by default + values: + - ./values/minimal_values.yaml + asserts: + - matchSnapshot: { } + - it: should render network-policy with custom egress policy + values: + - ./values/minimal_values.yaml + set: + networkPolicy: + egress: + - to: + - ipBlock: + cidr: 10.0.0.0/24 + ports: + - protocol: TCP + port: 5978 + asserts: + - matchSnapshot: { } + - it: should render network-policy with custom ingress policy + values: + - ./values/minimal_values.yaml + set: + networkPolicy: + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + "kubernetes.io/metadata.name": myingresscontroller + ports: + - protocol: TCP + port: http + - protocol: TCP + port: https + asserts: + - matchSnapshot: { } diff --git a/chart/tests/pdb_test.yaml b/chart/tests/pdb_test.yaml new file mode 100644 index 0000000..e3e58b8 --- /dev/null +++ b/chart/tests/pdb_test.yaml @@ -0,0 +1,38 @@ +suite: test pdb +templates: + - pdb.yaml +tests: + - it: should not render pdb if disabled + values: + - ./values/minimal_values.yaml + set: + pod.disruptionBudget.enabled: false + asserts: + - hasDocuments: + count: 0 + - it: should render pdb by default + values: + - ./values/minimal_values.yaml + asserts: + - matchSnapshot: { } + - it: should fail if minAvailable >= replicas + values: + - ./values/minimal_values.yaml + set: + pod: + replicas: 2 + disruptionBudget: + minAvailable: 2 + asserts: + - failedTemplate: + errorMessage: "replicaCount must be greater than pod.disruptionBudget.minAvailable!" + - it: should render with minAvailable + values: + - ./values/minimal_values.yaml + set: + pod: + disruptionBudget: + minAvailable: 1 + maxUnavailable: 0 + asserts: + - matchSnapshot: { } diff --git a/chart/tests/route_test.yaml b/chart/tests/route_test.yaml new file mode 100644 index 0000000..bae4386 --- /dev/null +++ b/chart/tests/route_test.yaml @@ -0,0 +1,71 @@ +suite: test route +templates: + - route.yaml +tests: + - it: should not render route by default + asserts: + - hasDocuments: + count: 0 + - it: should fail to render if enabled but http.hosts are not defined + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + asserts: + - failedTemplate: + errorMessage: "http.hosts is required if route is enabled!" + - it: should render route with default values if enabled + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + http: + hosts: + "test.example.com": {} + asserts: + - matchSnapshot: { } + - it: should render targetPort with http if tls.termination is set to edge + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + tls: + termination: edge + http: + hosts: + "test.example.com": { } + asserts: + - matchSnapshot: { } + - it: should support overriding tls.insecureEdgeTerminationPolicy + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + tls: + insecureEdgeTerminationPolicy: None + http: + hosts: + "test.example.com": { } + asserts: + - matchSnapshot: { } + - it: should render multiple routes with default values if enabled + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + http: + hosts: + "test1.example.com": { } + "test2.example.com": { } + asserts: + - matchSnapshot: { } diff --git a/chart/tests/service_test.yaml b/chart/tests/service_test.yaml new file mode 100644 index 0000000..222eba2 --- /dev/null +++ b/chart/tests/service_test.yaml @@ -0,0 +1,63 @@ +suite: test service +templates: + - service.yaml +tests: + - it: should render minimal values + values: + - ./values/minimal_values.yaml + asserts: + - matchSnapshot: {} + - it: should support alternative http port + values: + - ./values/minimal_values.yaml + set: + service.httpPort: 8080 + asserts: + - matchSnapshot: {} + - it: should support alternative https port + values: + - ./values/minimal_values.yaml + set: + service.httpsPort: 8443 + asserts: + - matchSnapshot: {} + - it: should support httpNodePort + values: + - ./values/minimal_values.yaml + set: + service: + type: NodePort + httpNodePort: 8080 + asserts: + - matchSnapshot: {} + - it: should support httpsNodePort + values: + - ./values/minimal_values.yaml + set: + service: + type: NodePort + httpsNodePort: 8443 + asserts: + - matchSnapshot: { } + - it: should set serving-cert-secret-name annotation if openshift.route is enabled and openshift.route.tls.termination is set to reencrypt + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + tls: + termination: reencrypt + asserts: + - matchSnapshot: { } + - it: should not set serving-cert-secret-name annotation if openshift.route is enabled, but openshift.route.tls.termination is not set to reencrypt + values: + - ./values/minimal_values.yaml + set: + openshift: + route: + enabled: true + tls: + termination: edge + asserts: + - matchSnapshot: { } diff --git a/chart/tests/values/minimal_values.yaml b/chart/tests/values/minimal_values.yaml new file mode 100644 index 0000000..25469cd --- /dev/null +++ b/chart/tests/values/minimal_values.yaml @@ -0,0 +1,5 @@ +pod: + container: + image: + repository: spa-example + tag: latest diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..060811b --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,133 @@ +# Explicit name of the application (not the application instance) +nameOverride: "" +# Explicit fully qualified name of the application (not the application instance) +fullnameOverride: "" + +config: + default: + base_href: "/" + spa_config: + endpoints: {} + http: + enabled: true + port: 80 + https: + enabled: false + port: 443 + ssl_certificate: /var/run/secrets/tls/tls.crt + ssl_certificate_key: /var/run/secrets/tls/tls.key + +# These labels are added to all resource types +labels: + "app.kubernetes.io/component": "frontend" + # The default value of "app.kubernetes.io/version" is pod.container.image.tag + # "app.kubernetes.io/version": "" + +pod: + labels: { } + annotations: { } + # Use 2 as replicaCount to avoid downtime during node maintenance, but use little resources as requests + replicaCount: 2 + securityContext: + runAsNonRoot: true + # Additional volumes on the output Deployment definition. + volumes: [ ] + # - name: foo + # secret: + # secretName: mysecret + # optional: false + chartVolumes: + configOut: + medium: "Memory" + sizeLimit: "256Mi" + tmp: + medium: "Memory" + sizeLimit: "2Gi" + container: + image: + repository: + pullPolicy: Always + tag: + pullSecrets: [ ] + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + livenessProbe: + httpGet: + path: /health/liveness + port: http + readinessProbe: + httpGet: + path: /health/readiness + port: http + volumeMounts: + # - name: foo + # mountPath: "/etc/foo" + # readOnly: true + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 100m + memory: 512Mi + nodeSelector: { } + tolerations: [ ] + affinity: { } + disruptionBudget: + enabled: true + maxUnavailable: 1 + minAvailable: 0 + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 4 + targetCPUUtilizationPercentage: 50 + customMetrics: [] + +service: + type: ClusterIP + httpPort: 80 + httpNodePort: + httpsPort: 443 + httpsNodePort: + annotations: { } + +networkPolicy: + enabled: true + policyTypes: + - Egress + # - Ingress + egress: [ ] + ingress: [ ] + # - from: + # - ipBlock: + # cidr: "172.17.0.0/32" # ingress-controller or load-balancer ip + # - namespaceSelector: + # matchLabels: + # "kubernetes.io/metadata.name": my-ingress-controller-namespace + +http: + hosts: + # "chart-example.local": + # path: / + # tlsSecretName: "" + +ingress: + # Enables Ingress + enabled: false + className: "" + targetServicePortName: http + annotations: { } + +openshift: + route: + # Enables OpenShift Route + enabled: false + annotations: { } + tls: + termination: reencrypt + insecureEdgeTerminationPolicy: Redirect