Compare commits
169
Commits
No files matched your search
+10
-3
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["github>jellyfin/.github//renovate-presets/default"],
|
||||||
"github>jellyfin/.github//renovate-presets/default"
|
"customManagers": [
|
||||||
|
{
|
||||||
|
"customType": "regex",
|
||||||
|
"fileMatch": ["charts/jellyfin/Chart.yaml"],
|
||||||
|
"matchStrings": ["appVersion: (?<currentValue>.*?)\\n"],
|
||||||
|
"datasourceTemplate": "docker",
|
||||||
|
"depNameTemplate": "jellyfin/jellyfin"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -17,12 +17,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5
|
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Helm Docs GitHub Action
|
- name: Helm Docs GitHub Action
|
||||||
uses: losisin/helm-docs-github-action@e88ef7459fa747a6153be98b0e77cd84087b0c87 # v1.3.1
|
uses: losisin/helm-docs-github-action@a57fae5676e4c55a228ea654a1bcaec8dd3cf5b5 # v1.6.2
|
||||||
with:
|
with:
|
||||||
git-push: true
|
git-push: true
|
||||||
git-commit-message: "docs(charts): :memo: write updated reademe"
|
git-commit-message: "docs(charts): :memo: write updated reademe"
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
name: PR Checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'charts/**'
|
||||||
|
- '.github/workflows/pr-checks.yaml'
|
||||||
|
- 'ct.yaml'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4
|
||||||
|
with:
|
||||||
|
version: v3.14.0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
# Check 1: Verify helm-docs is up to date
|
||||||
|
- name: Verify helm-docs up to date
|
||||||
|
uses: losisin/helm-docs-github-action@v1
|
||||||
|
with:
|
||||||
|
fail-on-diff: true
|
||||||
|
|
||||||
|
# Check 2: Run helm-unittest tests
|
||||||
|
- name: Install helm-unittest plugin
|
||||||
|
run: helm plugin install https://github.com/helm-unittest/helm-unittest.git
|
||||||
|
|
||||||
|
- name: Run helm-unittest
|
||||||
|
run: |
|
||||||
|
helm unittest charts/jellyfin --with-subchart=false
|
||||||
|
|
||||||
|
# Check 3: Helm lint (includes schema validation)
|
||||||
|
- name: Helm lint
|
||||||
|
run: helm lint charts/jellyfin
|
||||||
|
|
||||||
|
# Check 4: Helm template validation
|
||||||
|
- name: Helm template validation
|
||||||
|
run: helm template test charts/jellyfin --debug > /dev/null
|
||||||
|
|
||||||
|
# Check 5: Chart-testing (ct lint)
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2
|
||||||
|
|
||||||
|
- name: Run chart-testing (list-changed)
|
||||||
|
id: list-changed
|
||||||
|
run: |
|
||||||
|
changed=$(ct list-changed --target-branch master --config ct.yaml)
|
||||||
|
if [[ -n "$changed" ]]; then
|
||||||
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
if: steps.list-changed.outputs.changed == 'true'
|
||||||
|
run: ct lint --config ct.yaml
|
||||||
|
|
||||||
|
# Check 6: Kubernetes manifest validation with kubeconform
|
||||||
|
- name: Install kubeconform
|
||||||
|
run: |
|
||||||
|
# renovate: datasource=github-releases depName=yannh/kubeconform
|
||||||
|
VERSION=v0.6.7
|
||||||
|
wget -qO- https://github.com/yannh/kubeconform/releases/download/${VERSION}/kubeconform-linux-amd64.tar.gz | tar xz
|
||||||
|
sudo mv kubeconform /usr/local/bin/
|
||||||
|
kubeconform -v
|
||||||
|
|
||||||
|
- name: Validate Kubernetes manifests
|
||||||
|
run: |
|
||||||
|
# Validate against multiple Kubernetes versions (last 5 stable releases)
|
||||||
|
for k8s_version in 1.30.0 1.31.0 1.32.0 1.33.0 1.34.0; do
|
||||||
|
echo "Validating against Kubernetes $k8s_version"
|
||||||
|
helm template test charts/jellyfin \
|
||||||
|
| kubeconform -strict -summary -kubernetes-version $k8s_version
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check 7: Markdown lint
|
||||||
|
- name: Lint markdown files
|
||||||
|
uses: DavidAnson/markdownlint-cli2-action@v18
|
||||||
|
with:
|
||||||
|
globs: 'charts/**/*.md'
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -28,13 +28,132 @@ jobs:
|
|||||||
git config --global user.email "team@jellyfin.org"
|
git config --global user.email "team@jellyfin.org"
|
||||||
|
|
||||||
- name: Install Helm
|
- name: Install Helm
|
||||||
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4
|
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Install yq
|
||||||
|
run: |
|
||||||
|
# renovate: datasource=github-releases depName=mikefarah/yq
|
||||||
|
YQ_VERSION="4.44.3"
|
||||||
|
wget -q https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 -O yq
|
||||||
|
chmod +x yq
|
||||||
|
sudo mv yq /usr/local/bin/yq
|
||||||
|
yq --version
|
||||||
|
|
||||||
- name: Run chart-releaser
|
- name: Run chart-releaser
|
||||||
uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0
|
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
|
||||||
env:
|
env:
|
||||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
with:
|
with:
|
||||||
config: .github/cr.yaml
|
config: .github/cr.yaml
|
||||||
|
|
||||||
|
- name: Extract chart metadata and format release notes
|
||||||
|
id: release-notes
|
||||||
|
run: |
|
||||||
|
CHART_PATH="charts/jellyfin"
|
||||||
|
CHART_NAME=$(yq eval '.name' "${CHART_PATH}/Chart.yaml")
|
||||||
|
CHART_VERSION=$(yq eval '.version' "${CHART_PATH}/Chart.yaml")
|
||||||
|
APP_VERSION=$(yq eval '.appVersion' "${CHART_PATH}/Chart.yaml")
|
||||||
|
RELEASE_TAG="${CHART_NAME}-${CHART_VERSION}"
|
||||||
|
|
||||||
|
echo "tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
CHANGELOG_RAW=$(yq eval '.annotations."artifacthub.io/changes"' "${CHART_PATH}/Chart.yaml")
|
||||||
|
|
||||||
|
# Build changelog section
|
||||||
|
CHANGELOG_SECTION=""
|
||||||
|
if [ "$CHANGELOG_RAW" != "null" ] && [ -n "$CHANGELOG_RAW" ]; then
|
||||||
|
CHANGELOG_SECTION="## What's Changed
|
||||||
|
|
||||||
|
"
|
||||||
|
TEMP_FILE=$(mktemp)
|
||||||
|
SEPARATOR="|"
|
||||||
|
echo "$CHANGELOG_RAW" | yq eval ".[] | .kind + \"${SEPARATOR}\" + .description" - > "$TEMP_FILE"
|
||||||
|
|
||||||
|
while IFS="${SEPARATOR}" read -r kind description; do
|
||||||
|
case "$kind" in
|
||||||
|
"added")
|
||||||
|
emoji="✨"
|
||||||
|
label="Added"
|
||||||
|
;;
|
||||||
|
"changed")
|
||||||
|
emoji="🔄"
|
||||||
|
label="Changed"
|
||||||
|
;;
|
||||||
|
"deprecated")
|
||||||
|
emoji="⚠️"
|
||||||
|
label="Deprecated"
|
||||||
|
;;
|
||||||
|
"removed")
|
||||||
|
emoji="🗑️"
|
||||||
|
label="Removed"
|
||||||
|
;;
|
||||||
|
"fixed")
|
||||||
|
emoji="🐛"
|
||||||
|
label="Fixed"
|
||||||
|
;;
|
||||||
|
"security")
|
||||||
|
emoji="🔒"
|
||||||
|
label="Security"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
emoji="📝"
|
||||||
|
label="Changed"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
CHANGELOG_SECTION="${CHANGELOG_SECTION}- ${emoji} **${label}**: ${description}
|
||||||
|
"
|
||||||
|
done < "$TEMP_FILE"
|
||||||
|
|
||||||
|
rm -f "$TEMP_FILE"
|
||||||
|
CHANGELOG_SECTION="${CHANGELOG_SECTION}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build release body
|
||||||
|
RELEASE_BODY="## Jellyfin Helm Chart v${CHART_VERSION}
|
||||||
|
|
||||||
|
**Application Version**: \`${APP_VERSION}\`
|
||||||
|
|
||||||
|
${CHANGELOG_SECTION}## Installation
|
||||||
|
|
||||||
|
Add the Jellyfin Helm repository:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
|
||||||
|
helm repo update
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Install the chart:
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
helm install my-jellyfin jellyfin/jellyfin --version ${CHART_VERSION}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Upgrade
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
helm upgrade my-jellyfin jellyfin/jellyfin --version ${CHART_VERSION}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
📦 **Chart**: \`jellyfin\` v${CHART_VERSION}
|
||||||
|
🎬 **Jellyfin**: v${APP_VERSION}
|
||||||
|
📖 **Documentation**: https://github.com/jellyfin/jellyfin-helm/blob/main/charts/jellyfin/README.md
|
||||||
|
"
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
echo "$RELEASE_BODY" > release-notes.md
|
||||||
|
|
||||||
|
- name: Update release notes
|
||||||
|
env:
|
||||||
|
GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
run: |
|
||||||
|
RELEASE_TAG="${{ steps.release-notes.outputs.tag }}"
|
||||||
|
|
||||||
|
# Update release notes
|
||||||
|
gh release edit "$RELEASE_TAG" --notes-file release-notes.md
|
||||||
|
|
||||||
|
echo "✅ Updated release notes for $RELEASE_TAG"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# markdownlint configuration
|
||||||
|
# https://github.com/DavidAnson/markdownlint
|
||||||
|
|
||||||
|
default: true
|
||||||
|
|
||||||
|
# MD013/line-length - Line length (disabled for technical docs)
|
||||||
|
MD013: false
|
||||||
|
|
||||||
|
# MD022/blanks-around-headings - Disabled for helm-docs generated content
|
||||||
|
MD022: false
|
||||||
|
|
||||||
|
# MD031/blanks-around-fences - Disabled for helm-docs generated content
|
||||||
|
MD031: false
|
||||||
|
|
||||||
|
# MD032/blanks-around-lists - Disabled for helm-docs generated content
|
||||||
|
MD032: false
|
||||||
|
|
||||||
|
# MD033/no-inline-html
|
||||||
|
MD033: false
|
||||||
|
|
||||||
|
# MD034/no-bare-urls - Disabled for helm-docs generated content
|
||||||
|
MD034: false
|
||||||
|
|
||||||
|
# MD041/first-line-heading
|
||||||
|
MD041: false
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# yamllint configuration
|
||||||
|
# https://yamllint.readthedocs.io/
|
||||||
|
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
ignore: |
|
||||||
|
charts/*/templates/
|
||||||
|
.github/
|
||||||
|
|
||||||
|
rules:
|
||||||
|
line-length:
|
||||||
|
max: 120
|
||||||
|
level: warning
|
||||||
|
document-start: disable
|
||||||
|
truthy:
|
||||||
|
allowed-values: ['true', 'false', 'on', 'off']
|
||||||
|
comments:
|
||||||
|
min-spaces-from-content: 1
|
||||||
|
indentation:
|
||||||
|
spaces: 2
|
||||||
|
indent-sequences: true
|
||||||
@@ -7,7 +7,7 @@ This repository contains Helm charts for the jellyfin project.
|
|||||||
Each Helm chart is stored in its own directory. Each chart follows the standard Helm chart structure:
|
Each Helm chart is stored in its own directory. Each chart follows the standard Helm chart structure:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
hellyfin-helm/
|
jellyfin-helm/
|
||||||
├── jellyfin/
|
├── jellyfin/
|
||||||
│ ├── Chart.yaml
|
│ ├── Chart.yaml
|
||||||
│ ├── values.yaml
|
│ ├── values.yaml
|
||||||
@@ -32,7 +32,7 @@ hellyfin-helm/
|
|||||||
To use the charts in this repository, first add the repository to Helm:
|
To use the charts in this repository, first add the repository to Helm:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add jellyfin https://<your-repo-url>.git
|
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 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/
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 10.9.11
|
|
||||||
description: Jellyfin Media Server
|
|
||||||
name: jellyfin
|
name: jellyfin
|
||||||
version: 1.3.0
|
description: A Helm chart for Jellyfin Media Server
|
||||||
type: application
|
type: application
|
||||||
home: https://jellyfin.org/
|
home: https://jellyfin.org/
|
||||||
icon: https://jellyfin.org/images/logo.svg
|
icon: https://jellyfin.org/images/logo.svg
|
||||||
@@ -10,3 +8,51 @@ keywords:
|
|||||||
- jellyfin
|
- jellyfin
|
||||||
- media
|
- media
|
||||||
- self-hosted
|
- self-hosted
|
||||||
|
version: 3.0.0
|
||||||
|
maintainers:
|
||||||
|
- name: Jellyfin Project
|
||||||
|
url: https://jellyfin.org
|
||||||
|
appVersion: "10.11.5"
|
||||||
|
annotations:
|
||||||
|
artifacthub.io/links: |
|
||||||
|
- name: Documentation
|
||||||
|
url: https://jellyfin.org/docs/
|
||||||
|
- name: GitHub Repository
|
||||||
|
url: https://github.com/jellyfin/jellyfin
|
||||||
|
- name: Helm Chart Repository
|
||||||
|
url: https://github.com/jellyfin/jellyfin-helm
|
||||||
|
- name: Support
|
||||||
|
url: https://jellyfin.org/contact/
|
||||||
|
artifacthub.io/maintainers: |
|
||||||
|
- name: Jellyfin Project
|
||||||
|
url: https://jellyfin.org
|
||||||
|
artifacthub.io/changes: |
|
||||||
|
- kind: changed
|
||||||
|
description: Update Jellyfin to 10.11.5
|
||||||
|
- kind: deprecated
|
||||||
|
description: Deprecate initContainers parameter in favor of extraInitContainers for consistency (will be removed after 2030)
|
||||||
|
- kind: fixed
|
||||||
|
description: Fix extraInitContainers not working (was using wrong parameter name in template)
|
||||||
|
- kind: added
|
||||||
|
description: Add NetworkPolicy support for network isolation and security hardening
|
||||||
|
links:
|
||||||
|
- name: Documentation
|
||||||
|
url: https://kubernetes.io/docs/concepts/services-networking/network-policies/
|
||||||
|
- kind: added
|
||||||
|
description: Add troubleshooting documentation for inotify instance limits with workaround example
|
||||||
|
- kind: added
|
||||||
|
description: Add support for Gateway API HTTPRoute resource
|
||||||
|
- kind: added
|
||||||
|
description: Add envFrom support to load environment variables from ConfigMap or Secret
|
||||||
|
- kind: added
|
||||||
|
description: Add NOTES.txt with helpful post-installation information and deprecation warnings
|
||||||
|
- kind: added
|
||||||
|
description: Add startup probe to prevent pod restarts during slow initial startup with large media libraries
|
||||||
|
- kind: added
|
||||||
|
description: Add persistence.cache configuration for dedicated cache volume support
|
||||||
|
- kind: added
|
||||||
|
description: Add comprehensive IPv6 and dual-stack networking documentation
|
||||||
|
- kind: added
|
||||||
|
description: Add revisionHistoryLimit parameter to control rollback history retention (defaults to 3 instead of Kubernetes default 10)
|
||||||
|
- kind: changed
|
||||||
|
description: Improve health probe configuration examples for IPv6 compatibility
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{{description}}
|
||||||
|
Copyright (C) {{year}} {{fullname}}
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
{signature of Ty Coon}, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Migration Guide
|
||||||
|
|
||||||
|
## v1.x to v2.x
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
1. The `enableDLNA` key has been moved from the top level to the `jellyfin` subkey.
|
||||||
|
2. Some renames have occurred to align with the standard:
|
||||||
|
```yaml
|
||||||
|
extraPodLabels -> podLabels
|
||||||
|
extraPodAnnotations -> podAnnotations
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The `extraEnvVars` key has been moved and renamed to `jellyfin.env`.
|
||||||
|
2. `extraVolumes` has been moved to `volumes`, and `extraVolumeMounts` has been moved to `volumeMounts`.
|
||||||
|
3. The `extraExistingClaimMounts` key has been removed, as it can now be represented with `volumes` and `volumeMounts`.
|
||||||
|
|
||||||
|
### Service
|
||||||
|
|
||||||
|
1. The `name` field has been renamed to `portName` for consistency.
|
||||||
|
2. The following fields have been added:
|
||||||
|
```yaml
|
||||||
|
# -- Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/
|
||||||
|
ipFamilyPolicy: ""
|
||||||
|
# -- Supported IP families (IPv4, IPv6).
|
||||||
|
ipFamilies: []
|
||||||
|
# -- Class of the LoadBalancer.
|
||||||
|
loadBalancerClass: ""
|
||||||
|
# -- External traffic policy (Cluster or Local).
|
||||||
|
# externalTrafficPolicy: Cluster
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ingress
|
||||||
|
|
||||||
|
1. The `className` field has been added for ingress class, as annotations are deprecated.
|
||||||
|
2. The `path` field has been moved under the `hosts` key to better represent the actual CRD, providing more fine-grained control.
|
||||||
|
3. The `labels` field has been added for additional labels.
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
|
||||||
|
PVC creation is now enabled by default to prevent data loss when the chart is used without a specific configuration.
|
||||||
|
|
||||||
|
### Probes
|
||||||
|
|
||||||
|
The liveness and readiness probes are now always enabled to ensure proper Kubernetes lifecycle management. Adjust the values accordingly if you have a large library.
|
||||||
+456
-72
@@ -1,96 +1,480 @@
|
|||||||
# jellyfin
|
# jellyfin
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
Jellyfin Media Server
|
A Helm chart for Jellyfin Media Server
|
||||||
|
|
||||||
**Homepage:** <https://jellyfin.org/>
|
**Homepage:** <https://jellyfin.org/>
|
||||||
|
|
||||||
## Steps to Use a Helm Chart
|
## Quick Start
|
||||||
|
|
||||||
### 1. Add a Helm Repository
|
|
||||||
|
|
||||||
Helm repositories contain collections of charts. You can add an existing repository using the following command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add <repo-name> <repo-url>
|
# Add the Jellyfin Helm repository
|
||||||
|
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
# Install with default settings (ephemeral storage)
|
||||||
|
helm install my-jellyfin jellyfin/jellyfin
|
||||||
|
|
||||||
|
# Install with persistent storage
|
||||||
|
helm install my-jellyfin jellyfin/jellyfin \
|
||||||
|
--set persistence.config.enabled=true \
|
||||||
|
--set persistence.media.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Install the Helm Chart
|
For production deployments, create a custom `values.yaml` file with your configuration and install using:
|
||||||
|
|
||||||
To install a chart, use the following command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm install <release-name> <repo-name>/jellyfin
|
helm install my-jellyfin jellyfin/jellyfin --values values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. View the Installation
|
## Maintainers
|
||||||
|
|
||||||
You can check the status of the release using:
|
This chart is maintained by the [Jellyfin Project](https://jellyfin.org).
|
||||||
|
|
||||||
```bash
|
|
||||||
helm status <release-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customizing the Chart
|
|
||||||
|
|
||||||
Helm charts come with default values, but you can customize them by using the --set flag or by providing a custom values.yaml file.
|
|
||||||
|
|
||||||
### 1. Using --set to Override Values
|
|
||||||
```bash
|
|
||||||
helm install <release-name> <chart-name> --set key1=value1,key2=value2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Using a values.yaml File
|
|
||||||
You can create a custom values.yaml file and pass it to the install command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install <release-name> <chart-name> -f values.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Values
|
## Values
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
|-----|------|---------|-------------|
|
|-----|------|---------|-------------|
|
||||||
| affinity | object | `{}` | |
|
| affinity | object | `{}` | Affinity rules for pod scheduling. |
|
||||||
| enableDLNA | bool | `false` | Setting this to true enables DLNA which requires the pod to be attached to the host network in order to be useful - this can break things like ingress to the service https://jellyfin.org/docs/general/networking/dlna.html |
|
| deploymentAnnotations | object | `{}` | Annotations to add to the deployment. |
|
||||||
| extraContainers | object | `{}` | additional sidecar containers to run inside the pod. |
|
| deploymentStrategy | object | `{"type":"RollingUpdate"}` | Deployment strategy configuration. See `kubectl explain deployment.spec.strategy`. |
|
||||||
| extraEnvVars | list | `[]` | aditional environment variables passed to the pod |
|
| dnsConfig | object | `{}` | Define a dnsConfig. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config Use this to provide a custom DNS resolver configuration |
|
||||||
| extraPodAnnotations | object | `{}` | additional annotations applied to the pod |
|
| dnsPolicy | string | `""` | Define a dnsPolicy. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy |
|
||||||
| extraPodLabels | object | `{}` | additional pod labels. Not used as a selector label. |
|
| extraContainers | list | `[]` | additional sidecar containers to run inside the pod. |
|
||||||
| extraVolumeMounts | list | `[]` | Define mount points for additional volumes. |
|
| extraInitContainers | list | `[]` | Additional init containers to run inside the pod. Init containers run before the main application container starts. See: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ Example: extraInitContainers: - name: init-config image: busybox:1.35 command: ['sh', '-c', 'echo "Initializing..." && sleep 5'] volumeMounts: - name: config mountPath: /config |
|
||||||
| extraVolumes | list | `[]` | Define additional volumes to mount to the pod. |
|
| fullnameOverride | string | `""` | Override the default full name of the chart. |
|
||||||
| fullnameOverride | string | `""` | |
|
| httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":[],"parentRefs":[],"rules":[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | HTTPRoute configuration for Gateway API. See: https://gateway-api.sigs.k8s.io/ |
|
||||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
| httpRoute.hostnames | list | `[]` | Hostnames to match for this HTTPRoute |
|
||||||
| image.repository | string | `"docker.io/jellyfin/jellyfin"` | Set cutom repository for jellyfin image |
|
| httpRoute.parentRefs | list | `[]` | Gateway references to attach this HTTPRoute to |
|
||||||
| image.tag | string | `""` | Set jellyfin version which should be required since chart is updated with new versions automatically |
|
| httpRoute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Rules for routing traffic |
|
||||||
| ingress.annotations | object | `{}` | |
|
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy (Always, IfNotPresent, or Never). |
|
||||||
| ingress.enabled | bool | `false` | |
|
| image.repository | string | `"docker.io/jellyfin/jellyfin"` | Container image repository for Jellyfin. |
|
||||||
| ingress.hosts[0] | string | `"chart-example.local"` | |
|
| image.tag | string | `""` | Jellyfin container image tag. Leave empty to automatically use the Chart's app version. |
|
||||||
| ingress.labels | object | `{}` | |
|
| imagePullSecrets | list | `[]` | Image pull secrets to authenticate with private repositories. See: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ |
|
||||||
| ingress.path | string | `"/"` | |
|
| ingress | object | `{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}],"tls":[]}` | Ingress configuration. See: https://kubernetes.io/docs/concepts/services-networking/ingress/ |
|
||||||
| ingress.tls | list | `[]` | |
|
| initContainers | list | `[]` | DEPRECATED: Use extraInitContainers instead. Will be removed after 2030. @deprecated - This parameter is deprecated, use extraInitContainers instead |
|
||||||
| livenessProbe | object | `{"enabled":true,"initialDelaySeconds":10}` | Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds. |
|
| jellyfin.args | list | `[]` | Additional arguments for the entrypoint command. |
|
||||||
| nameOverride | string | `""` | |
|
| jellyfin.command | list | `[]` | Custom command to use as container entrypoint. |
|
||||||
| nodeSelector | object | `{}` | |
|
| jellyfin.enableDLNA | bool | `false` | Enable DLNA. Requires host network. See: https://jellyfin.org/docs/general/networking/dlna.html |
|
||||||
|
| jellyfin.env | list | `[]` | Additional environment variables for the container. Example: Workaround for inotify limits (see Troubleshooting section in README) Example: env: - name: JELLYFIN_CACHE_DIR value: /cache |
|
||||||
|
| jellyfin.envFrom | list | `[]` | Load environment variables from ConfigMap or Secret. See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables Example: envFrom: - configMapRef: name: jellyfin-config - secretRef: name: jellyfin-secrets |
|
||||||
|
| livenessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe for Jellyfin. This probe is disabled during startup (startup probe handles initial checks). Uses httpGet for compatibility with both IPv4 and IPv6. |
|
||||||
|
| metrics | object | `{"enabled":false,"serviceMonitor":{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}}` | Configuration for metrics collection and monitoring |
|
||||||
|
| metrics.enabled | bool | `false` | Enable or disable metrics collection |
|
||||||
|
| metrics.serviceMonitor | object | `{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}` | Configuration for the Prometheus ServiceMonitor |
|
||||||
|
| metrics.serviceMonitor.enabled | bool | `false` | Enable or disable the creation of a ServiceMonitor resource |
|
||||||
|
| metrics.serviceMonitor.interval | string | `"30s"` | Interval at which metrics should be scraped |
|
||||||
|
| metrics.serviceMonitor.labels | object | `{}` | Labels to add to the ServiceMonitor resource |
|
||||||
|
| metrics.serviceMonitor.metricRelabelings | list | `[]` | Relabeling rules for the metrics before ingestion |
|
||||||
|
| metrics.serviceMonitor.namespace | string | `""` | Namespace where the ServiceMonitor resource should be created. Defaults to Release.Namespace |
|
||||||
|
| metrics.serviceMonitor.path | string | `"/metrics"` | Path to scrape for metrics |
|
||||||
|
| metrics.serviceMonitor.port | int | `8096` | Port to scrape for metrics |
|
||||||
|
| metrics.serviceMonitor.relabelings | list | `[]` | Relabeling rules for the scraped metrics |
|
||||||
|
| metrics.serviceMonitor.scheme | string | `"http"` | Scheme to use for scraping metrics (http or https) |
|
||||||
|
| metrics.serviceMonitor.scrapeTimeout | string | `"30s"` | Timeout for scraping metrics |
|
||||||
|
| metrics.serviceMonitor.targetLabels | list | `[]` | Target labels to add to the scraped metrics |
|
||||||
|
| metrics.serviceMonitor.tlsConfig | object | `{}` | TLS configuration for scraping metrics |
|
||||||
|
| nameOverride | string | `""` | Override the default name of the chart. |
|
||||||
|
| networkPolicy | object | `{"egress":{"allowAllEgress":true,"allowDNS":true,"customRules":[],"dnsNamespace":"kube-system","dnsPodSelector":{"k8s-app":"kube-dns"},"restrictedEgress":{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}},"enabled":false,"ingress":{"allowExternal":true,"customRules":[],"namespaceSelector":{},"podSelector":{}},"metrics":{"namespace":"","podSelector":{"app.kubernetes.io/name":"prometheus"}},"policyTypes":["Ingress","Egress"]}` | Network Policy configuration for network isolation and security. Requires a CNI plugin that supports NetworkPolicy (Calico, Cilium, Weave, etc.). WARNING: NetworkPolicy cannot be enabled when hostNetwork is used (DLNA mode). The chart will fail with an error if both are enabled simultaneously. |
|
||||||
|
| networkPolicy.egress | object | `{"allowAllEgress":true,"allowDNS":true,"customRules":[],"dnsNamespace":"kube-system","dnsPodSelector":{"k8s-app":"kube-dns"},"restrictedEgress":{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}}` | Egress rules configuration - controls what external connections Jellyfin can make. |
|
||||||
|
| networkPolicy.egress.allowAllEgress | bool | `true` | Allow all egress traffic (internet access for metadata, subtitles, images). When true, Jellyfin can connect to any external destination (0.0.0.0/0). This is the recommended default as Jellyfin needs internet access for: - Downloading movie/TV show metadata (TMDB, TheTVDB, OMDb) - Fetching poster images, fanart, and other artwork - Downloading subtitles (OpenSubtitles) - Updating plugins When false, you must configure restrictedEgress or customRules. |
|
||||||
|
| networkPolicy.egress.allowDNS | bool | `true` | Allow DNS resolution (required for Jellyfin to function). This adds an egress rule for kube-system namespace with kube-dns pods. DNS is required for resolving metadata provider domains and subtitle services. It is highly recommended to keep this enabled. |
|
||||||
|
| networkPolicy.egress.customRules | list | `[]` | Additional custom egress rules. Allows for complex scenarios not covered by the standard template. These rules are added as-is to the NetworkPolicy egress section. Example - allow connections to specific database: customRules: - to: - podSelector: matchLabels: app: postgresql ports: - protocol: TCP port: 5432 |
|
||||||
|
| networkPolicy.egress.dnsNamespace | string | `"kube-system"` | DNS namespace where DNS service is running. Usually "kube-system" but can differ in some Kubernetes distributions. |
|
||||||
|
| networkPolicy.egress.dnsPodSelector | object | `{"k8s-app":"kube-dns"}` | DNS pod selector labels. Default selector is for kube-dns, but CoreDNS or other DNS providers may use different labels. Adjust if needed. Common alternatives: k8s-app: kube-dns (default) k8s-app: coredns app.kubernetes.io/name: coredns |
|
||||||
|
| networkPolicy.egress.restrictedEgress | object | `{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}` | Restricted egress mode for security-conscious deployments. Only used when allowAllEgress is false. Provides fine-grained control over outbound connections. |
|
||||||
|
| networkPolicy.egress.restrictedEgress.allowInCluster | bool | `true` | Allow communication within the cluster (pod-to-pod). Useful if Jellyfin needs to connect to other services in the cluster. This allows connections to any pod in any namespace. |
|
||||||
|
| networkPolicy.egress.restrictedEgress.allowMetadata | bool | `true` | Allow HTTPS (443/TCP) for metadata providers. Most metadata providers (TMDB, TheTVDB, OpenSubtitles, Fanart.tv) use HTTPS, so this covers the majority of use cases. This allows connections to any IP on port 443. |
|
||||||
|
| networkPolicy.egress.restrictedEgress.allowedCIDRs | list | `[]` | Additional IP CIDR blocks to allow egress. Useful for allowing specific IP ranges for metadata providers or other external services. Example - allow entire internet except private networks: allowedCIDRs: - 0.0.0.0/0 Example - allow specific metadata provider IP ranges: allowedCIDRs: - 13.224.0.0/14 # CloudFront (used by many CDNs) |
|
||||||
|
| networkPolicy.enabled | bool | `false` | Enable NetworkPolicy for the Jellyfin pod. By default, this is disabled to maintain backward compatibility. When enabled, you can control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress). |
|
||||||
|
| networkPolicy.ingress | object | `{"allowExternal":true,"customRules":[],"namespaceSelector":{},"podSelector":{}}` | Ingress rules configuration - controls which pods/namespaces can access Jellyfin. |
|
||||||
|
| networkPolicy.ingress.allowExternal | bool | `true` | Allow external access from any namespace and any pod. When true, any pod in the cluster can access Jellyfin (default behavior). When false, only pods matching podSelector/namespaceSelector can access. Set to false for production environments to restrict access. |
|
||||||
|
| networkPolicy.ingress.customRules | list | `[]` | Additional custom ingress rules. Allows for complex scenarios not covered by the standard template. These rules are added as-is to the NetworkPolicy ingress section. Example - allow from monitoring namespace: customRules: - from: - namespaceSelector: matchLabels: name: monitoring ports: - protocol: TCP port: 8096 |
|
||||||
|
| networkPolicy.ingress.namespaceSelector | object | `{}` | Namespace selector to allow cross-namespace ingress. Only used when allowExternal is false. Allows you to specify which namespaces can access Jellyfin. Example - only allow from ingress-nginx namespace: namespaceSelector: matchLabels: name: ingress-nginx |
|
||||||
|
| networkPolicy.ingress.podSelector | object | `{}` | Custom pod selector for allowed ingress traffic. Only used when allowExternal is false. Allows you to specify which pods can access Jellyfin based on labels. Example - only allow pods with specific label: podSelector: matchLabels: jellyfin-client: "true" |
|
||||||
|
| networkPolicy.metrics | object | `{"namespace":"","podSelector":{"app.kubernetes.io/name":"prometheus"}}` | Prometheus metrics scraping configuration. Automatically allows ingress from Prometheus when metrics.serviceMonitor.enabled is true. This ensures Prometheus can scrape metrics without additional configuration. |
|
||||||
|
| networkPolicy.metrics.namespace | string | `""` | Namespace where Prometheus is running. Leave empty to use the same namespace as Jellyfin. Set to the monitoring namespace if Prometheus is in a different namespace. Example: "monitoring" or "prometheus" |
|
||||||
|
| networkPolicy.metrics.podSelector | object | `{"app.kubernetes.io/name":"prometheus"}` | Pod selector for Prometheus pods. These labels must match your Prometheus deployment. The chart will automatically add an ingress rule for pods matching these labels. Default selector works with prometheus-operator and kube-prometheus-stack. Adjust if your Prometheus uses different labels. |
|
||||||
|
| networkPolicy.policyTypes | list | `["Ingress","Egress"]` | Policy types to enforce. Both ingress and egress policies can be enabled. See: https://kubernetes.io/docs/concepts/services-networking/network-policies/#policy-types |
|
||||||
|
| nodeSelector | object | `{}` | Node selector for pod scheduling. |
|
||||||
|
| persistence.cache.accessMode | string | `"ReadWriteOnce"` | PVC specific settings, only used if type is 'pvc'. |
|
||||||
|
| persistence.cache.annotations | object | `{}` | Custom annotations to be added to the PVC |
|
||||||
|
| persistence.cache.enabled | bool | `false` | set to false to use emptyDir |
|
||||||
|
| persistence.cache.hostPath | string | `""` | Path on the host node for cache storage, only used if type is 'hostPath'. |
|
||||||
|
| persistence.cache.size | string | `"10Gi"` | |
|
||||||
|
| persistence.cache.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
|
||||||
|
| persistence.cache.type | string | `"pvc"` | Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. |
|
||||||
| persistence.config.accessMode | string | `"ReadWriteOnce"` | |
|
| persistence.config.accessMode | string | `"ReadWriteOnce"` | |
|
||||||
| persistence.config.enabled | bool | `false` | |
|
| persistence.config.annotations | object | `{}` | Custom annotations to be added to the PVC |
|
||||||
| persistence.config.size | string | `"1Gi"` | |
|
| persistence.config.enabled | bool | `true` | set to false to use emptyDir |
|
||||||
| persistence.config.storageClass | string | `"-"` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
|
| persistence.config.size | string | `"5Gi"` | |
|
||||||
| persistence.extraExistingClaimMounts | list | `[]` | Add aditional PVs/PVCs to our container. Check values.yaml to see an example. |
|
| persistence.config.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
|
||||||
| persistence.media.accessMode | string | `"ReadWriteOnce"` | |
|
| persistence.media.accessMode | string | `"ReadWriteOnce"` | PVC specific settings, only used if type is 'pvc'. |
|
||||||
| persistence.media.enabled | bool | `false` | |
|
| persistence.media.annotations | object | `{}` | Custom annotations to be added to the PVC |
|
||||||
| persistence.media.size | string | `"10Gi"` | |
|
| persistence.media.enabled | bool | `true` | set to false to use emptyDir |
|
||||||
| persistence.media.storageClass | string | `"-"` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
|
| persistence.media.hostPath | string | `""` | Path on the host node for media storage, only used if type is 'hostPath'. |
|
||||||
| readinessProbe | object | `{"enabled":true,"initialDelaySeconds":10}` | Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds. |
|
| persistence.media.size | string | `"25Gi"` | |
|
||||||
| replicaCount | int | `1` | Number of jellyfin replicas to start. Should be left at 1 |
|
| persistence.media.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
|
||||||
| resources | object | `{}` | |
|
| persistence.media.type | string | `"pvc"` | Type of volume for media storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. |
|
||||||
| service.annotations | object | `{}` | |
|
| podAnnotations | object | `{}` | Annotations to add to the pod. |
|
||||||
| service.labels | object | `{}` | |
|
| podLabels | object | `{}` | Additional labels to add to the pod. |
|
||||||
| service.loadBalancerIP | string | `nil` | |
|
| podPrivileges | object | `{"hostIPC":false,"hostNetwork":false,"hostPID":false}` | Privileged pod settings for advanced use cases |
|
||||||
| service.port | int | `8096` | |
|
| podPrivileges.hostIPC | bool | `false` | Enable hostIPC namespace. Required for NVIDIA MPS (Multi-Process Service) GPU sharing. See: https://docs.nvidia.com/deploy/mps/index.html |
|
||||||
| service.type | string | `"ClusterIP"` | |
|
| podPrivileges.hostNetwork | bool | `false` | Enable hostNetwork. Allows pod to use the host's network namespace. |
|
||||||
| tolerations | list | `[]` | |
|
| podPrivileges.hostPID | bool | `false` | Enable hostPID namespace. Allows pod to see processes on the host. |
|
||||||
|
| podSecurityContext | object | `{}` | Security context for the pod. |
|
||||||
|
| priorityClassName | string | `""` | Define a priorityClassName for the pod. |
|
||||||
|
| readinessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure readiness probe for Jellyfin. This probe is disabled during startup (startup probe handles initial checks). Uses httpGet for compatibility with both IPv4 and IPv6. |
|
||||||
|
| replicaCount | int | `1` | Number of Jellyfin replicas to start. Should be left at 1. |
|
||||||
|
| resources | object | `{}` | Resource requests and limits for the Jellyfin container. |
|
||||||
|
| revisionHistoryLimit | int | `3` | Number of old ReplicaSets to retain for rollback history. Set to 0 to disable revision history (not recommended). If not specified, Kubernetes defaults to 10. See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit |
|
||||||
|
| runtimeClassName | string | `""` | Define a custom runtimeClassName for the pod. |
|
||||||
|
| securityContext | object | `{}` | Security context for the container. |
|
||||||
|
| service.annotations | object | `{}` | Annotations for the service. |
|
||||||
|
| service.ipFamilies | list | `[]` | Supported IP families (IPv4, IPv6). Examples: IPv4 only: ["IPv4"] IPv6 only: ["IPv6"] Dual-stack (IPv4 primary): ["IPv4", "IPv6"] Dual-stack (IPv6 primary): ["IPv6", "IPv4"] Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket) |
|
||||||
|
| service.ipFamilyPolicy | string | `""` | Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ Options: SingleStack, PreferDualStack, RequireDualStack For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"] For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"] |
|
||||||
|
| service.labels | object | `{}` | Labels for the service. |
|
||||||
|
| service.loadBalancerClass | string | `""` | Class of the LoadBalancer. |
|
||||||
|
| service.loadBalancerIP | string | `""` | Specific IP address for the LoadBalancer. |
|
||||||
|
| service.loadBalancerSourceRanges | list | `[]` | Source ranges allowed to access the LoadBalancer. |
|
||||||
|
| service.port | int | `8096` | Port for the Jellyfin service. |
|
||||||
|
| service.portName | string | `"service"` | Name of the port in the service. |
|
||||||
|
| service.type | string | `"ClusterIP"` | Service type (ClusterIP, NodePort, or LoadBalancer). |
|
||||||
|
| serviceAccount | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | Service account configuration. See: https://kubernetes.io/docs/concepts/security/service-accounts/ |
|
||||||
|
| serviceAccount.annotations | object | `{}` | Annotations for the service account. |
|
||||||
|
| serviceAccount.automount | bool | `true` | Automatically mount API credentials for the service account. |
|
||||||
|
| serviceAccount.create | bool | `true` | Specifies whether to create a service account. |
|
||||||
|
| serviceAccount.name | string | `""` | Custom name for the service account. If left empty, the name will be autogenerated. |
|
||||||
|
| startupProbe | object | `{"failureThreshold":30,"initialDelaySeconds":0,"periodSeconds":10,"tcpSocket":{"port":"http"}}` | Configure startup probe for Jellyfin. This probe gives Jellyfin enough time to start, especially with large media libraries. After the startup probe succeeds once, liveness and readiness probes take over. |
|
||||||
|
| tolerations | list | `[]` | Tolerations for pod scheduling. |
|
||||||
|
| volumeMounts | list | `[]` | Additional volume mounts for the Jellyfin container. |
|
||||||
|
| volumes | list | `[]` | Additional volumes to mount in the Jellyfin pod. |
|
||||||
|
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2)
|
Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2)
|
||||||
|
|
||||||
|
## Gateway API HTTPRoute
|
||||||
|
|
||||||
|
This chart supports the Kubernetes Gateway API HTTPRoute resource as a modern alternative to Ingress.
|
||||||
|
|
||||||
|
To use HTTPRoute, you need to have Gateway API CRDs installed in your cluster and a Gateway resource configured.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
httpRoute:
|
||||||
|
enabled: true
|
||||||
|
annotations: {}
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
namespace: gateway-system
|
||||||
|
sectionName: https
|
||||||
|
hostnames:
|
||||||
|
- jellyfin.example.com
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about Gateway API, see: <https://gateway-api.sigs.k8s.io/>
|
||||||
|
|
||||||
|
## Hardware acceleration
|
||||||
|
|
||||||
|
Out of the box the pod does not have the necessary permissions to enable hardware acceleration (HWA) in Jellyfin.
|
||||||
|
Adding the following Helm values should make it enable you to use hardware acceleration features.
|
||||||
|
Some settings may need to be tweaked depending on the type of device (Intel/AMD/NVIDIA/...) and your container runtime.
|
||||||
|
|
||||||
|
Please refer to the Jellyfin upstream documentation for more information about hardware acceleration: <https://jellyfin.org/docs/general/administration/hardware-acceleration/>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- "SYS_ADMIN"
|
||||||
|
drop:
|
||||||
|
- "ALL"
|
||||||
|
privileged: false
|
||||||
|
|
||||||
|
extraVolumes:
|
||||||
|
- name: hwa
|
||||||
|
hostPath:
|
||||||
|
path: /dev/dri
|
||||||
|
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: hwa
|
||||||
|
mountPath: /dev/dri
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Security
|
||||||
|
|
||||||
|
Jellyfin chart supports Kubernetes NetworkPolicy for network isolation and security hardening. NetworkPolicy allows you to control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress).
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **CNI Plugin**: NetworkPolicy requires a Container Network Interface (CNI) plugin that supports NetworkPolicies, such as:
|
||||||
|
- Calico
|
||||||
|
- Cilium
|
||||||
|
- Weave Net
|
||||||
|
- Canal
|
||||||
|
|
||||||
|
Check with your cluster administrator if NetworkPolicy is supported in your cluster.
|
||||||
|
|
||||||
|
- **DLNA Incompatibility**: NetworkPolicy cannot be enabled when `enableDLNA: true` or `podPrivileges.hostNetwork: true` is set, as pods using `hostNetwork` bypass NetworkPolicy rules. The chart will fail deployment with a clear error message if both are enabled.
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
By default, NetworkPolicy is disabled to maintain backward compatibility. To enable basic network isolation:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a NetworkPolicy with the following defaults:
|
||||||
|
- **Ingress**: Allow connections from any pod in any namespace
|
||||||
|
- **Egress**: Allow DNS resolution and all internet access (required for metadata)
|
||||||
|
|
||||||
|
### Production Configuration - Restrict to Ingress Controller
|
||||||
|
|
||||||
|
For production environments, you typically want to restrict access to only allow traffic through the Ingress controller:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: ingress-nginx
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
### High Security Configuration - Restricted Egress
|
||||||
|
|
||||||
|
For security-conscious deployments that need to limit outbound connections:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
jellyfin-client: "true" # Only pods with this label can access
|
||||||
|
egress:
|
||||||
|
allowDNS: true # Always needed
|
||||||
|
allowAllEgress: false # Block unrestricted internet
|
||||||
|
restrictedEgress:
|
||||||
|
allowMetadata: true # Allow HTTPS/443 for metadata providers (TMDB, etc.)
|
||||||
|
allowInCluster: false # Block pod-to-pod communication
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: With this configuration, Jellyfin can only:
|
||||||
|
- Resolve DNS queries
|
||||||
|
- Connect to HTTPS (port 443) endpoints for metadata providers
|
||||||
|
- Cannot connect to other pods in the cluster
|
||||||
|
- Cannot access non-HTTPS services
|
||||||
|
|
||||||
|
### Monitoring Integration
|
||||||
|
|
||||||
|
If you're using Prometheus for monitoring, the chart automatically allows ingress from Prometheus pods when metrics are enabled:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the chart allows ingress from pods with label `app.kubernetes.io/name: prometheus`. If your Prometheus uses different labels, customize the selector:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
metrics:
|
||||||
|
namespace: monitoring # If Prometheus is in a different namespace
|
||||||
|
podSelector:
|
||||||
|
app: my-prometheus
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
#### Multiple Namespaces Access
|
||||||
|
|
||||||
|
Allow access from multiple namespaces using custom rules:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
customRules:
|
||||||
|
# Frontend namespace
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: frontend
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
access-jellyfin: "true"
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
|
||||||
|
# Admin tools namespace
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: admin-tools
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Egress Rules
|
||||||
|
|
||||||
|
Allow connections to specific external services:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
egress:
|
||||||
|
allowAllEgress: false
|
||||||
|
restrictedEgress:
|
||||||
|
allowMetadata: true
|
||||||
|
allowedCIDRs:
|
||||||
|
- 10.0.0.0/8 # Internal network
|
||||||
|
- 192.168.0.0/16 # Another internal network
|
||||||
|
customRules:
|
||||||
|
# Allow connection to external database
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 203.0.113.0/24
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
|
||||||
|
1. **Metadata Providers**: Jellyfin requires internet access to download metadata (movie posters, descriptions, etc.) from:
|
||||||
|
- TheMovieDB (api.themoviedb.org)
|
||||||
|
- TheTVDB (api.thetvdb.com)
|
||||||
|
- OpenSubtitles (api.opensubtitles.com)
|
||||||
|
- Fanart.tv (fanart.tv)
|
||||||
|
|
||||||
|
If you use `restrictedEgress.allowMetadata: true`, these will work as they all use HTTPS (port 443).
|
||||||
|
|
||||||
|
2. **DNS Access**: DNS resolution is critical for Jellyfin operation. The chart prevents accidental DNS blocking by defaulting `allowDNS: true`.
|
||||||
|
|
||||||
|
3. **Local Metadata**: If you want to completely block internet access, you can use local metadata (NFO files and local images). This requires manual setup and is not the default Jellyfin behavior.
|
||||||
|
|
||||||
|
4. **Testing**: Always test NetworkPolicy changes in a development environment first. Misconfigured policies can block legitimate traffic.
|
||||||
|
|
||||||
|
### NetworkPolicy Troubleshooting
|
||||||
|
|
||||||
|
**Jellyfin can't download metadata/images:**
|
||||||
|
- Check that `egress.allowAllEgress: true` or `restrictedEgress.allowMetadata: true` is set
|
||||||
|
- Verify DNS egress is allowed: `egress.allowDNS: true`
|
||||||
|
|
||||||
|
**Can't access Jellyfin web interface:**
|
||||||
|
- Verify ingress rules allow traffic from your access point (Ingress controller, LoadBalancer, etc.)
|
||||||
|
- Check NOTES.txt after deployment for detailed NetworkPolicy status
|
||||||
|
|
||||||
|
**Prometheus can't scrape metrics:**
|
||||||
|
- Ensure `metrics.enabled: true` and `metrics.serviceMonitor.enabled: true`
|
||||||
|
- Verify `networkPolicy.metrics.podSelector` matches your Prometheus labels
|
||||||
|
- Set `networkPolicy.metrics.namespace` if Prometheus is in a different namespace
|
||||||
|
|
||||||
|
**Deployment fails with "NetworkPolicy cannot be enabled...":**
|
||||||
|
- You have both `networkPolicy.enabled: true` and `hostNetwork: true` (or `enableDLNA: true`)
|
||||||
|
- NetworkPolicy doesn't work with host networking
|
||||||
|
- Either disable NetworkPolicy or disable host networking
|
||||||
|
|
||||||
|
For more configuration options, see the full values documentation in [values.yaml](values.yaml).
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### inotify Instance Limit Reached
|
||||||
|
|
||||||
|
**Problem:** Jellyfin crashes with error:
|
||||||
|
|
||||||
|
```text
|
||||||
|
System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** The Linux kernel has a limit on inotify instances (file system watchers) per user. Jellyfin uses inotify to monitor media libraries for changes.
|
||||||
|
|
||||||
|
**Proper solution (recommended):**
|
||||||
|
|
||||||
|
Increase the inotify limit on the Kubernetes nodes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Temporary (until reboot)
|
||||||
|
sysctl -w fs.inotify.max_user_instances=512
|
||||||
|
|
||||||
|
# Permanent
|
||||||
|
echo "fs.inotify.max_user_instances=512" >> /etc/sysctl.conf
|
||||||
|
sysctl -p
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended values:
|
||||||
|
- `fs.inotify.max_user_instances`: 512 or higher
|
||||||
|
- `fs.inotify.max_user_watches`: 524288 or higher (if you have large media libraries)
|
||||||
|
|
||||||
|
**Workaround (if you cannot modify host settings):**
|
||||||
|
|
||||||
|
If you're running on a managed Kubernetes cluster where you cannot modify node-level settings, you can force Jellyfin to use polling instead of inotify. **Note: This is less efficient and may increase CPU usage and delay change detection.**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jellyfin:
|
||||||
|
env:
|
||||||
|
- name: DOTNET_USE_POLLING_FILE_WATCHER
|
||||||
|
value: "1"
|
||||||
|
```
|
||||||
|
|
||||||
|
This workaround disables inotify file watching in favor of periodic polling, which doesn't require inotify instances but is less efficient.
|
||||||
|
|
||||||
|
## IPv6 Configuration
|
||||||
|
|
||||||
|
This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6.
|
||||||
|
|
||||||
|
### IPv6-only Configuration
|
||||||
|
|
||||||
|
For IPv6-only clusters:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service:
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dual-stack Configuration
|
||||||
|
|
||||||
|
For dual-stack clusters (both IPv4 and IPv6):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service:
|
||||||
|
ipFamilyPolicy: PreferDualStack # or RequireDualStack
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
- IPv6 # First family in the list is the primary
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about Kubernetes dual-stack networking, see: <https://kubernetes.io/docs/concepts/services-networking/dual-stack/>
|
||||||
@@ -7,49 +7,31 @@
|
|||||||
|
|
||||||
{{ template "chart.homepageLine" . }}
|
{{ template "chart.homepageLine" . }}
|
||||||
|
|
||||||
## Steps to Use a Helm Chart
|
## Quick Start
|
||||||
|
|
||||||
### 1. Add a Helm Repository
|
|
||||||
|
|
||||||
Helm repositories contain collections of charts. You can add an existing repository using the following command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm repo add <repo-name> <repo-url>
|
# Add the Jellyfin Helm repository
|
||||||
|
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
# Install with default settings (ephemeral storage)
|
||||||
|
helm install my-jellyfin jellyfin/jellyfin
|
||||||
|
|
||||||
|
# Install with persistent storage
|
||||||
|
helm install my-jellyfin jellyfin/jellyfin \
|
||||||
|
--set persistence.config.enabled=true \
|
||||||
|
--set persistence.media.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Install the Helm Chart
|
For production deployments, create a custom `values.yaml` file with your configuration and install using:
|
||||||
|
|
||||||
To install a chart, use the following command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm install <release-name> <repo-name>/jellyfin
|
helm install my-jellyfin jellyfin/jellyfin --values values.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. View the Installation
|
## Maintainers
|
||||||
|
|
||||||
You can check the status of the release using:
|
This chart is maintained by the [Jellyfin Project](https://jellyfin.org).
|
||||||
|
|
||||||
```bash
|
|
||||||
helm status <release-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customizing the Chart
|
|
||||||
|
|
||||||
Helm charts come with default values, but you can customize them by using the --set flag or by providing a custom values.yaml file.
|
|
||||||
|
|
||||||
### 1. Using --set to Override Values
|
|
||||||
```bash
|
|
||||||
helm install <release-name> <chart-name> --set key1=value1,key2=value2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Using a values.yaml File
|
|
||||||
You can create a custom values.yaml file and pass it to the install command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install <release-name> <chart-name> -f values.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
{{ template "chart.maintainersSection" . }}
|
|
||||||
|
|
||||||
{{ template "chart.sourcesSection" . }}
|
{{ template "chart.sourcesSection" . }}
|
||||||
|
|
||||||
@@ -58,3 +40,328 @@ helm install <release-name> <chart-name> -f values.yaml
|
|||||||
{{ template "chart.valuesSection" . }}
|
{{ template "chart.valuesSection" . }}
|
||||||
|
|
||||||
{{ template "helm-docs.versionFooter" . }}
|
{{ template "helm-docs.versionFooter" . }}
|
||||||
|
|
||||||
|
## Gateway API HTTPRoute
|
||||||
|
|
||||||
|
This chart supports the Kubernetes Gateway API HTTPRoute resource as a modern alternative to Ingress.
|
||||||
|
|
||||||
|
To use HTTPRoute, you need to have Gateway API CRDs installed in your cluster and a Gateway resource configured.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
httpRoute:
|
||||||
|
enabled: true
|
||||||
|
annotations: {}
|
||||||
|
parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
namespace: gateway-system
|
||||||
|
sectionName: https
|
||||||
|
hostnames:
|
||||||
|
- jellyfin.example.com
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about Gateway API, see: <https://gateway-api.sigs.k8s.io/>
|
||||||
|
|
||||||
|
## Hardware acceleration
|
||||||
|
|
||||||
|
Out of the box the pod does not have the necessary permissions to enable hardware acceleration (HWA) in Jellyfin.
|
||||||
|
Adding the following Helm values should make it enable you to use hardware acceleration features.
|
||||||
|
Some settings may need to be tweaked depending on the type of device (Intel/AMD/NVIDIA/...) and your container runtime.
|
||||||
|
|
||||||
|
Please refer to the Jellyfin upstream documentation for more information about hardware acceleration: <https://jellyfin.org/docs/general/administration/hardware-acceleration/>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- "SYS_ADMIN"
|
||||||
|
drop:
|
||||||
|
- "ALL"
|
||||||
|
privileged: false
|
||||||
|
|
||||||
|
extraVolumes:
|
||||||
|
- name: hwa
|
||||||
|
hostPath:
|
||||||
|
path: /dev/dri
|
||||||
|
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: hwa
|
||||||
|
mountPath: /dev/dri
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Security
|
||||||
|
|
||||||
|
Jellyfin chart supports Kubernetes NetworkPolicy for network isolation and security hardening. NetworkPolicy allows you to control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress).
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- **CNI Plugin**: NetworkPolicy requires a Container Network Interface (CNI) plugin that supports NetworkPolicies, such as:
|
||||||
|
- Calico
|
||||||
|
- Cilium
|
||||||
|
- Weave Net
|
||||||
|
- Canal
|
||||||
|
|
||||||
|
Check with your cluster administrator if NetworkPolicy is supported in your cluster.
|
||||||
|
|
||||||
|
- **DLNA Incompatibility**: NetworkPolicy cannot be enabled when `enableDLNA: true` or `podPrivileges.hostNetwork: true` is set, as pods using `hostNetwork` bypass NetworkPolicy rules. The chart will fail deployment with a clear error message if both are enabled.
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
By default, NetworkPolicy is disabled to maintain backward compatibility. To enable basic network isolation:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a NetworkPolicy with the following defaults:
|
||||||
|
- **Ingress**: Allow connections from any pod in any namespace
|
||||||
|
- **Egress**: Allow DNS resolution and all internet access (required for metadata)
|
||||||
|
|
||||||
|
### Production Configuration - Restrict to Ingress Controller
|
||||||
|
|
||||||
|
For production environments, you typically want to restrict access to only allow traffic through the Ingress controller:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: ingress-nginx
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
### High Security Configuration - Restricted Egress
|
||||||
|
|
||||||
|
For security-conscious deployments that need to limit outbound connections:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
jellyfin-client: "true" # Only pods with this label can access
|
||||||
|
egress:
|
||||||
|
allowDNS: true # Always needed
|
||||||
|
allowAllEgress: false # Block unrestricted internet
|
||||||
|
restrictedEgress:
|
||||||
|
allowMetadata: true # Allow HTTPS/443 for metadata providers (TMDB, etc.)
|
||||||
|
allowInCluster: false # Block pod-to-pod communication
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: With this configuration, Jellyfin can only:
|
||||||
|
- Resolve DNS queries
|
||||||
|
- Connect to HTTPS (port 443) endpoints for metadata providers
|
||||||
|
- Cannot connect to other pods in the cluster
|
||||||
|
- Cannot access non-HTTPS services
|
||||||
|
|
||||||
|
### Monitoring Integration
|
||||||
|
|
||||||
|
If you're using Prometheus for monitoring, the chart automatically allows ingress from Prometheus pods when metrics are enabled:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the chart allows ingress from pods with label `app.kubernetes.io/name: prometheus`. If your Prometheus uses different labels, customize the selector:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
metrics:
|
||||||
|
namespace: monitoring # If Prometheus is in a different namespace
|
||||||
|
podSelector:
|
||||||
|
app: my-prometheus
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
#### Multiple Namespaces Access
|
||||||
|
|
||||||
|
Allow access from multiple namespaces using custom rules:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
customRules:
|
||||||
|
# Frontend namespace
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: frontend
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
access-jellyfin: "true"
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
|
||||||
|
# Admin tools namespace
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: admin-tools
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Egress Rules
|
||||||
|
|
||||||
|
Allow connections to specific external services:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
egress:
|
||||||
|
allowAllEgress: false
|
||||||
|
restrictedEgress:
|
||||||
|
allowMetadata: true
|
||||||
|
allowedCIDRs:
|
||||||
|
- 10.0.0.0/8 # Internal network
|
||||||
|
- 192.168.0.0/16 # Another internal network
|
||||||
|
customRules:
|
||||||
|
# Allow connection to external database
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 203.0.113.0/24
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
|
||||||
|
1. **Metadata Providers**: Jellyfin requires internet access to download metadata (movie posters, descriptions, etc.) from:
|
||||||
|
- TheMovieDB (api.themoviedb.org)
|
||||||
|
- TheTVDB (api.thetvdb.com)
|
||||||
|
- OpenSubtitles (api.opensubtitles.com)
|
||||||
|
- Fanart.tv (fanart.tv)
|
||||||
|
|
||||||
|
If you use `restrictedEgress.allowMetadata: true`, these will work as they all use HTTPS (port 443).
|
||||||
|
|
||||||
|
2. **DNS Access**: DNS resolution is critical for Jellyfin operation. The chart prevents accidental DNS blocking by defaulting `allowDNS: true`.
|
||||||
|
|
||||||
|
3. **Local Metadata**: If you want to completely block internet access, you can use local metadata (NFO files and local images). This requires manual setup and is not the default Jellyfin behavior.
|
||||||
|
|
||||||
|
4. **Testing**: Always test NetworkPolicy changes in a development environment first. Misconfigured policies can block legitimate traffic.
|
||||||
|
|
||||||
|
### NetworkPolicy Troubleshooting
|
||||||
|
|
||||||
|
**Jellyfin can't download metadata/images:**
|
||||||
|
- Check that `egress.allowAllEgress: true` or `restrictedEgress.allowMetadata: true` is set
|
||||||
|
- Verify DNS egress is allowed: `egress.allowDNS: true`
|
||||||
|
|
||||||
|
**Can't access Jellyfin web interface:**
|
||||||
|
- Verify ingress rules allow traffic from your access point (Ingress controller, LoadBalancer, etc.)
|
||||||
|
- Check NOTES.txt after deployment for detailed NetworkPolicy status
|
||||||
|
|
||||||
|
**Prometheus can't scrape metrics:**
|
||||||
|
- Ensure `metrics.enabled: true` and `metrics.serviceMonitor.enabled: true`
|
||||||
|
- Verify `networkPolicy.metrics.podSelector` matches your Prometheus labels
|
||||||
|
- Set `networkPolicy.metrics.namespace` if Prometheus is in a different namespace
|
||||||
|
|
||||||
|
**Deployment fails with "NetworkPolicy cannot be enabled...":**
|
||||||
|
- You have both `networkPolicy.enabled: true` and `hostNetwork: true` (or `enableDLNA: true`)
|
||||||
|
- NetworkPolicy doesn't work with host networking
|
||||||
|
- Either disable NetworkPolicy or disable host networking
|
||||||
|
|
||||||
|
For more configuration options, see the full values documentation in [values.yaml](values.yaml).
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### inotify Instance Limit Reached
|
||||||
|
|
||||||
|
**Problem:** Jellyfin crashes with error:
|
||||||
|
|
||||||
|
```text
|
||||||
|
System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** The Linux kernel has a limit on inotify instances (file system watchers) per user. Jellyfin uses inotify to monitor media libraries for changes.
|
||||||
|
|
||||||
|
**Proper solution (recommended):**
|
||||||
|
|
||||||
|
Increase the inotify limit on the Kubernetes nodes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Temporary (until reboot)
|
||||||
|
sysctl -w fs.inotify.max_user_instances=512
|
||||||
|
|
||||||
|
# Permanent
|
||||||
|
echo "fs.inotify.max_user_instances=512" >> /etc/sysctl.conf
|
||||||
|
sysctl -p
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended values:
|
||||||
|
- `fs.inotify.max_user_instances`: 512 or higher
|
||||||
|
- `fs.inotify.max_user_watches`: 524288 or higher (if you have large media libraries)
|
||||||
|
|
||||||
|
**Workaround (if you cannot modify host settings):**
|
||||||
|
|
||||||
|
If you're running on a managed Kubernetes cluster where you cannot modify node-level settings, you can force Jellyfin to use polling instead of inotify. **Note: This is less efficient and may increase CPU usage and delay change detection.**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jellyfin:
|
||||||
|
env:
|
||||||
|
- name: DOTNET_USE_POLLING_FILE_WATCHER
|
||||||
|
value: "1"
|
||||||
|
```
|
||||||
|
|
||||||
|
This workaround disables inotify file watching in favor of periodic polling, which doesn't require inotify instances but is less efficient.
|
||||||
|
|
||||||
|
## IPv6 Configuration
|
||||||
|
|
||||||
|
This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6.
|
||||||
|
|
||||||
|
### IPv6-only Configuration
|
||||||
|
|
||||||
|
For IPv6-only clusters:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service:
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dual-stack Configuration
|
||||||
|
|
||||||
|
For dual-stack clusters (both IPv4 and IPv6):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service:
|
||||||
|
ipFamilyPolicy: PreferDualStack # or RequireDualStack
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
- IPv6 # First family in the list is the primary
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about Kubernetes dual-stack networking, see: <https://kubernetes.io/docs/concepts/services-networking/dual-stack/>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Jellyfin Helm Chart Examples
|
||||||
|
|
||||||
|
This directory contains example configurations for common Jellyfin deployment scenarios.
|
||||||
|
|
||||||
|
## Available Examples
|
||||||
|
|
||||||
|
### 1. Minimal (`minimal.yaml`)
|
||||||
|
|
||||||
|
Quick start configuration with ephemeral storage for testing purposes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin -f examples/minimal.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use case**: Development, testing, demo environments.
|
||||||
|
|
||||||
|
**Note**: All data will be lost when the pod is restarted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Production (`production.yaml`)
|
||||||
|
|
||||||
|
Production-ready configuration with persistent storage, resource limits, and optimized health probes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin -f examples/production.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Persistent storage for config, media, and cache
|
||||||
|
- Resource requests and limits
|
||||||
|
- Optimized health probes for large libraries
|
||||||
|
- Security context with non-root user
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Hardware Acceleration (`hardware-acceleration.yaml`)
|
||||||
|
|
||||||
|
Enable GPU acceleration for video transcoding (Intel QuickSync example).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin -f examples/hardware-acceleration.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
|
||||||
|
- Node with compatible GPU (Intel/AMD/NVIDIA)
|
||||||
|
- GPU device plugin installed in cluster
|
||||||
|
- Appropriate node labels/taints
|
||||||
|
|
||||||
|
**Note**: Adjust device paths and capabilities for your specific GPU type. See [Jellyfin documentation](https://jellyfin.org/docs/general/administration/hardware-acceleration/) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Ingress with TLS (`ingress-tls.yaml`)
|
||||||
|
|
||||||
|
External access via HTTPS with automatic certificate management.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin -f examples/ingress-tls.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
|
||||||
|
- Ingress controller (e.g., ingress-nginx)
|
||||||
|
- cert-manager for automatic TLS certificates (optional)
|
||||||
|
- DNS record pointing to your cluster
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
|
||||||
|
- Update `jellyfin.example.com` to your actual domain
|
||||||
|
- Adjust cert-manager issuer if using different CA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. NetworkPolicy Security (`network-policy.yaml`)
|
||||||
|
|
||||||
|
Secure deployment with network isolation and controlled traffic flow.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin -f examples/network-policy.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
|
||||||
|
- CNI plugin with NetworkPolicy support (Calico, Cilium, Weave, Canal)
|
||||||
|
- Ingress controller with known namespace/labels
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Restrict ingress to Ingress controller only
|
||||||
|
- Allow egress for DNS and metadata providers (HTTPS/443)
|
||||||
|
- Block unrestricted pod-to-pod communication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Combining Examples
|
||||||
|
|
||||||
|
You can combine multiple example files to create custom configurations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin \
|
||||||
|
-f examples/production.yaml \
|
||||||
|
-f examples/hardware-acceleration.yaml \
|
||||||
|
-f examples/ingress-tls.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Files are merged in order, with later files overriding earlier ones.
|
||||||
|
|
||||||
|
## Custom Values
|
||||||
|
|
||||||
|
All examples can be further customized by overriding specific values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install jellyfin jellyfin/jellyfin \
|
||||||
|
-f examples/production.yaml \
|
||||||
|
--set persistence.media.size=1Ti \
|
||||||
|
--set resources.limits.memory=16Gi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
For more configuration options, see:
|
||||||
|
|
||||||
|
- [Chart README](../README.md)
|
||||||
|
- [values.yaml](../values.yaml) - Complete configuration reference
|
||||||
|
- [Jellyfin Documentation](https://jellyfin.org/docs/)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Hardware Acceleration (Intel QuickSync)
|
||||||
|
# Enables GPU acceleration for video transcoding
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Node with Intel GPU
|
||||||
|
# - GPU device plugin installed in cluster
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# helm install jellyfin jellyfin/jellyfin -f hardware-acceleration.yaml
|
||||||
|
#
|
||||||
|
# For other GPU types (AMD/NVIDIA), adjust device paths and security context accordingly.
|
||||||
|
# See: https://jellyfin.org/docs/general/administration/hardware-acceleration/
|
||||||
|
|
||||||
|
# Security context for hardware acceleration
|
||||||
|
securityContext:
|
||||||
|
# Required capabilities for GPU access
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
|
||||||
|
# Mount GPU device
|
||||||
|
volumes:
|
||||||
|
- name: render-device
|
||||||
|
hostPath:
|
||||||
|
path: /dev/dri
|
||||||
|
type: Directory
|
||||||
|
|
||||||
|
volumeMounts:
|
||||||
|
- name: render-device
|
||||||
|
mountPath: /dev/dri
|
||||||
|
|
||||||
|
# Optional: Resource limits for GPU
|
||||||
|
# Uncomment and adjust for your GPU device plugin
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# gpu.intel.com/i915: 1
|
||||||
|
# requests:
|
||||||
|
# gpu.intel.com/i915: 1
|
||||||
|
|
||||||
|
# Persistent storage
|
||||||
|
persistence:
|
||||||
|
config:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
media:
|
||||||
|
enabled: true
|
||||||
|
size: 500Gi
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
size: 50Gi # Larger cache for transcoding
|
||||||
|
|
||||||
|
# Transcoding environment variables (optional)
|
||||||
|
jellyfin:
|
||||||
|
env:
|
||||||
|
- name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: "https://jellyfin.example.com"
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Jellyfin with Ingress and TLS
|
||||||
|
# External access via HTTPS with certificate management
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Ingress controller installed (e.g., ingress-nginx)
|
||||||
|
# - cert-manager for TLS certificate automation (optional)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# helm install jellyfin jellyfin/jellyfin -f ingress-tls.yaml
|
||||||
|
|
||||||
|
# Ingress configuration
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
# cert-manager annotation for automatic certificate provisioning
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
# Recommended nginx settings for Jellyfin
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
|
||||||
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||||
|
proxy_set_header X-Forwarded-Protocol $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
|
||||||
|
hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
tls:
|
||||||
|
- secretName: jellyfin-tls
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
|
||||||
|
# Service configuration
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8096
|
||||||
|
|
||||||
|
# Persistent storage
|
||||||
|
persistence:
|
||||||
|
config:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
media:
|
||||||
|
enabled: true
|
||||||
|
size: 500Gi
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
size: 20Gi
|
||||||
|
|
||||||
|
# Set published server URL for external access
|
||||||
|
jellyfin:
|
||||||
|
env:
|
||||||
|
- name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: "https://jellyfin.example.com"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Minimal Jellyfin deployment
|
||||||
|
# Quick start with default settings and ephemeral storage
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# helm install jellyfin jellyfin/jellyfin -f minimal.yaml
|
||||||
|
|
||||||
|
# Use ephemeral storage for testing
|
||||||
|
persistence:
|
||||||
|
config:
|
||||||
|
enabled: false
|
||||||
|
media:
|
||||||
|
enabled: false
|
||||||
|
cache:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Basic service configuration
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8096
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Secure Jellyfin deployment with NetworkPolicy
|
||||||
|
# Network isolation with controlled ingress/egress
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - CNI plugin with NetworkPolicy support (Calico, Cilium, etc.)
|
||||||
|
# - Ingress controller running in ingress-nginx namespace
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# helm install jellyfin jellyfin/jellyfin -f network-policy.yaml
|
||||||
|
|
||||||
|
# NetworkPolicy configuration
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Ingress: Allow only from Ingress controller
|
||||||
|
ingress:
|
||||||
|
allowExternal: false
|
||||||
|
namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: ingress-nginx
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
|
||||||
|
# Egress: Restricted for security
|
||||||
|
egress:
|
||||||
|
allowDNS: true # Required for DNS resolution
|
||||||
|
allowAllEgress: false # Block unrestricted internet
|
||||||
|
restrictedEgress:
|
||||||
|
allowMetadata: true # Allow HTTPS/443 for TMDB, TheTVDB, etc.
|
||||||
|
allowInCluster: false # Block pod-to-pod communication
|
||||||
|
|
||||||
|
# Ingress for external access
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
# Persistent storage
|
||||||
|
persistence:
|
||||||
|
config:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
media:
|
||||||
|
enabled: true
|
||||||
|
size: 500Gi
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
size: 20Gi
|
||||||
|
|
||||||
|
# Security context
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 4Gi
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Production Jellyfin deployment
|
||||||
|
# Persistent storage, resource limits, and health probes
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# helm install jellyfin jellyfin/jellyfin -f production.yaml
|
||||||
|
|
||||||
|
# Persistent storage configuration
|
||||||
|
persistence:
|
||||||
|
config:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: "" # Use default storage class
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
|
||||||
|
media:
|
||||||
|
enabled: true
|
||||||
|
size: 500Gi
|
||||||
|
storageClass: ""
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
size: 20Gi
|
||||||
|
storageClass: ""
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
|
||||||
|
# Resource limits and requests
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 4000m
|
||||||
|
memory: 8Gi
|
||||||
|
requests:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
# Health probes with adjusted timeouts for large libraries
|
||||||
|
startupProbe:
|
||||||
|
enabled: true
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 60
|
||||||
|
|
||||||
|
livenessProbe:
|
||||||
|
enabled: true
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
|
readinessProbe:
|
||||||
|
enabled: true
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
|
# Security context
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
|
||||||
|
# Service configuration
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8096
|
||||||
@@ -1,19 +1,87 @@
|
|||||||
1. Get the application URL by running these commands:
|
{{- if .Values.initContainers }}
|
||||||
|
#############################################################################
|
||||||
|
## *** WARNING *** ##
|
||||||
|
## ##
|
||||||
|
## The 'initContainers' parameter is DEPRECATED! ##
|
||||||
|
## Please migrate to 'extraInitContainers' instead. ##
|
||||||
|
## ##
|
||||||
|
## Support for 'initContainers' will be removed after 2030. ##
|
||||||
|
## ##
|
||||||
|
## Migration guide: ##
|
||||||
|
## Old: initContainers: [...] ##
|
||||||
|
## New: extraInitContainers: [...] ##
|
||||||
|
## ##
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
|
{{- if and .Values.networkPolicy.enabled (or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork) }}
|
||||||
|
#############################################################################
|
||||||
|
## *** ERROR *** ##
|
||||||
|
## ##
|
||||||
|
## NetworkPolicy cannot be enabled with hostNetwork or DLNA! ##
|
||||||
|
## ##
|
||||||
|
## The deployment will FAIL. Please fix your configuration: ##
|
||||||
|
## - Disable networkPolicy.enabled, OR ##
|
||||||
|
## - Disable jellyfin.enableDLNA and podPrivileges.hostNetwork ##
|
||||||
|
## ##
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
|
Thank you for installing {{ .Chart.Name }}!
|
||||||
|
|
||||||
|
Your release is named {{ .Release.Name }}.
|
||||||
|
|
||||||
{{- if .Values.ingress.enabled }}
|
{{- if .Values.ingress.enabled }}
|
||||||
|
|
||||||
|
Jellyfin is available via Ingress at:
|
||||||
{{- range .Values.ingress.hosts }}
|
{{- range .Values.ingress.hosts }}
|
||||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if .Values.httpRoute.enabled }}
|
||||||
|
|
||||||
|
Jellyfin is available via Gateway API HTTPRoute.
|
||||||
|
{{- if .Values.httpRoute.hostnames }}
|
||||||
|
Hostnames configured:
|
||||||
|
{{- range .Values.httpRoute.hostnames }}
|
||||||
|
- {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
⚠ No hostnames configured. Set httpRoute.hostnames for external access.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- else if contains "NodePort" .Values.service.type }}
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "jellyfin.fullname" . }})
|
|
||||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
Get the Jellyfin URL by running:
|
||||||
echo http://$NODE_IP:$NODE_PORT
|
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "jellyfin.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo "Jellyfin URL: http://$NODE_IP:$NODE_PORT"
|
||||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
{{- 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 {{ include "jellyfin.fullname" . }}'
|
Get the Jellyfin URL by running:
|
||||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jellyfin.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
|
||||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
|
||||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "jellyfin.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jellyfin.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
Visit http://127.0.0.1:8080 to use your application
|
echo "Jellyfin URL: http://$SERVICE_IP:{{ .Values.service.port }}"
|
||||||
kubectl port-forward $POD_NAME 8080:80
|
{{- else }}
|
||||||
|
|
||||||
|
To access Jellyfin, use port-forwarding:
|
||||||
|
|
||||||
|
kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
|
||||||
|
|
||||||
|
Then visit http://localhost:{{ .Values.service.port }} in your browser.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if not .Values.persistence.config.enabled }}
|
||||||
|
|
||||||
|
⚠ WARNING: Configuration persistence is disabled. Data will be lost on pod restart.
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.persistence.media.enabled }}
|
||||||
|
|
||||||
|
⚠ WARNING: Media persistence is disabled. Media files will be lost on pod restart.
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
For more information:
|
||||||
|
Chart documentation: https://github.com/jellyfin/jellyfin-helm
|
||||||
|
Jellyfin docs: https://jellyfin.org/docs/
|
||||||
|
Get release info: helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
{{/* vim: set filetype=mustache: */}}
|
{{/*
|
||||||
|
Fail if command and metrics are set
|
||||||
|
*/}}
|
||||||
|
{{- if and .Values.image.command .Values.metrics.enabled }}
|
||||||
|
{{- fail "Can't use custom command and metrics in combination. They are exclusive features" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Expand the name of the chart.
|
Expand the name of the chart.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "jellyfin.name" -}}
|
{{- define "jellyfin.name" -}}
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Create a default fully qualified app name.
|
Create a default fully qualified app name.
|
||||||
@@ -12,21 +18,52 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
|
|||||||
If release name contains chart name it will be used as a full name.
|
If release name contains chart name it will be used as a full name.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "jellyfin.fullname" -}}
|
{{- define "jellyfin.fullname" -}}
|
||||||
{{- if .Values.fullnameOverride -}}
|
{{- if .Values.fullnameOverride }}
|
||||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
{{- else -}}
|
{{- else }}
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
{{- if contains $name .Release.Name -}}
|
{{- if contains $name .Release.Name }}
|
||||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
{{- else -}}
|
{{- else }}
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Create chart name and version as used by the chart label.
|
Create chart name and version as used by the chart label.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "jellyfin.chart" -}}
|
{{- define "jellyfin.chart" -}}
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "jellyfin.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
||||||
|
{{ include "jellyfin.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "jellyfin.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "jellyfin.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "jellyfin.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
{{- if and .Values.persistence.config.enabled (not .Values.persistence.config.existingClaim) }}
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: {{ template "jellyfin.fullname" . }}-config
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
|
||||||
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- {{ .Values.persistence.config.accessMode | quote }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: {{ .Values.persistence.config.size | quote }}
|
|
||||||
{{- if .Values.persistence.config.storageClass }}
|
|
||||||
{{- if (eq "-" .Values.persistence.config.storageClass) }}
|
|
||||||
storageClassName: ""
|
|
||||||
{{- else }}
|
|
||||||
storageClassName: "{{ .Values.persistence.config.storageClass }}"
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
@@ -2,149 +2,225 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "jellyfin.fullname" . }}
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
|
{{- with .Values.deploymentAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
spec:
|
spec:
|
||||||
|
{{- with .Values.deploymentStrategy }}
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
{{- toYaml . | trim | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- if not (eq .Values.revisionHistoryLimit nil) }}
|
||||||
|
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
|
||||||
|
{{- end }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
{{- include "jellyfin.selectorLabels" . | nindent 6 }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
{{- with .Values.podAnnotations }}
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- with .Values.extraPodLabels }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.extraPodAnnotations }}
|
|
||||||
annotations:
|
annotations:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 8 }}
|
||||||
|
{{- with .Values.podLabels }}
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
{{- if .Values.enableDLNA }}
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "jellyfin.serviceAccountName" . }}
|
||||||
|
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
|
||||||
|
{{- with .Values.podSecurityContext }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork }}
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.podPrivileges.hostIPC }}
|
||||||
|
hostIPC: true
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.podPrivileges.hostPID }}
|
||||||
|
hostPID: true
|
||||||
|
{{- end }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
|
{{- with .Values.securityContext }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
{{ if .Values.extraEnvVars }}
|
{{- with .Values.jellyfin.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.jellyfin.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.jellyfin.envFrom }}
|
||||||
|
envFrom:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.jellyfin.env }}
|
||||||
env:
|
env:
|
||||||
{{- toYaml .Values.extraEnvVars | nindent 12 }}
|
{{- toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.metrics.enabled }}
|
||||||
|
lifecycle:
|
||||||
|
postStart:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
echo "Waiting for system.xml to enable metrics..."
|
||||||
|
timeout=300 # 5 minutes timeout
|
||||||
|
interval=5 # Check every 5 seconds
|
||||||
|
elapsed=0
|
||||||
|
while [ ! -f /config/config/system.xml ]; do
|
||||||
|
if [ $elapsed -ge $timeout ]; then
|
||||||
|
echo "ERROR: Timed out waiting for /config/config/system.xml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep $interval
|
||||||
|
elapsed=$((elapsed + interval))
|
||||||
|
done
|
||||||
|
echo "Found /config/config/system.xml, enabling metrics..."
|
||||||
|
# Check if metrics are already enabled before running sed
|
||||||
|
if ! grep -q '<EnableMetrics>true</EnableMetrics>' /config/config/system.xml; then
|
||||||
|
sed 's,<EnableMetrics>false</EnableMetrics>,<EnableMetrics>true</EnableMetrics>,' -i /config/config/system.xml
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Metrics enabled successfully."
|
||||||
|
else
|
||||||
|
echo "ERROR: Failed to enable metrics in /config/config/system.xml" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Metrics already enabled."
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8096
|
containerPort: 8096
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
{{ if .Values.enableDLNA }}
|
{{- if .Values.jellyfin.enableDLNA }}
|
||||||
- name: dlna
|
- name: dlna
|
||||||
containerPort: 1900
|
containerPort: 1900
|
||||||
hostPort: 1900
|
hostPort: 1900
|
||||||
protocol: UDP
|
protocol: UDP
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.startupProbe }}
|
||||||
|
startupProbe:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ if .Values.livenessProbe.enabled }}
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
{{ if .Values.livenessProbe.initialDelaySeconds }}
|
{{- toYaml .Values.livenessProbe | nindent 12 }}
|
||||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.livenessProbe.periodSeconds }}
|
|
||||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.livenessProbe.timeoutSeconds }}
|
|
||||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.livenessProbe.failureThreshold }}
|
|
||||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.livenessProbe.successThreshold }}
|
|
||||||
successThreshold: {{ .Values.livenessProbe.successThreshold }}
|
|
||||||
{{- end }}
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.readinessProbe.enabled }}
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
{{ if .Values.readinessProbe.initialDelaySeconds }}
|
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
||||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
{{- with .Values.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ if .Values.readinessProbe.periodSeconds }}
|
|
||||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.readinessProbe.timeoutSeconds }}
|
|
||||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.readinessProbe.failureThreshold }}
|
|
||||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Values.readinessProbe.successThreshold }}
|
|
||||||
successThreshold: {{ .Values.readinessProbe.successThreshold }}
|
|
||||||
{{- end }}
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
{{- end }}
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /config
|
- mountPath: /config
|
||||||
name: config
|
name: config
|
||||||
{{- if .Values.persistence.config.subPath }}
|
|
||||||
subPath: {{ .Values.persistence.config.subPath }}
|
|
||||||
{{- end }}
|
|
||||||
- mountPath: /media
|
- mountPath: /media
|
||||||
name: media
|
name: media
|
||||||
{{- if .Values.persistence.media.subPath }}
|
- mountPath: /cache
|
||||||
subPath: {{ .Values.persistence.media.subPath }}
|
name: cache
|
||||||
|
{{- with .Values.volumeMounts }}
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- range .Values.persistence.extraExistingClaimMounts }}
|
{{- with .Values.extraContainers }}
|
||||||
- name: {{ .name }}
|
{{- toYaml . | nindent 8 }}
|
||||||
mountPath: {{ .mountPath }}
|
{{- end }}
|
||||||
readOnly: {{ .readOnly }}
|
{{- if or .Values.initContainers .Values.extraInitContainers }}
|
||||||
{{- end }}
|
# Support both initContainers (deprecated) and extraInitContainers for backward compatibility.
|
||||||
{{- if .Values.extraVolumeMounts }}
|
# TODO: Remove initContainers support after 2030
|
||||||
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
|
initContainers:
|
||||||
{{- end }}
|
{{- if .Values.initContainers }}
|
||||||
{{- if .Values.extraContainers }}
|
{{- toYaml .Values.initContainers | nindent 6 }}
|
||||||
{{- toYaml .Values.extraContainers | nindent 10 }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- if .Values.extraInitContainers }}
|
||||||
resources:
|
{{- toYaml .Values.extraInitContainers | nindent 6 }}
|
||||||
{{ toYaml .Values.resources | indent 12 }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
volumes:
|
volumes:
|
||||||
- name: config
|
- name: config
|
||||||
{{- if .Values.persistence.config.enabled }}
|
{{- if .Values.persistence.config.enabled }}
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ if .Values.persistence.config.existingClaim }}{{ .Values.persistence.config.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-config{{- end }}
|
claimName: {{ if .Values.persistence.config.existingClaim }}{{ .Values.persistence.config.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-config{{- end }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
- name: media
|
- name: media
|
||||||
{{- if .Values.persistence.media.enabled }}
|
{{- if .Values.persistence.media.enabled }}
|
||||||
persistentVolumeClaim:
|
{{- if eq .Values.persistence.media.type "pvc" }}
|
||||||
claimName: {{ if .Values.persistence.media.existingClaim }}{{ .Values.persistence.media.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-media{{- end }}
|
persistentVolumeClaim:
|
||||||
{{- else }}
|
claimName: {{ if .Values.persistence.media.existingClaim }}{{ .Values.persistence.media.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-media{{- end }}
|
||||||
emptyDir: {}
|
{{- else if eq .Values.persistence.media.type "hostPath" }}
|
||||||
{{- end }}
|
hostPath:
|
||||||
{{- range .Values.persistence.extraExistingClaimMounts }}
|
path: {{ .Values.persistence.media.hostPath }}
|
||||||
- name: {{ .name }}
|
type: Directory
|
||||||
persistentVolumeClaim:
|
{{- else }}
|
||||||
claimName: {{ .existingClaim }}
|
emptyDir: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.extraVolumes }}
|
{{- else }}
|
||||||
{{- toYaml .Values.extraVolumes | nindent 6 }}
|
emptyDir: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.nodeSelector }}
|
- name: cache
|
||||||
|
{{- if .Values.persistence.cache.enabled }}
|
||||||
|
{{- if eq .Values.persistence.cache.type "pvc" }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ if .Values.persistence.cache.existingClaim }}{{ .Values.persistence.cache.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-cache{{- end }}
|
||||||
|
{{- else if eq .Values.persistence.cache.type "hostPath" }}
|
||||||
|
hostPath:
|
||||||
|
path: {{ .Values.persistence.cache.hostPath }}
|
||||||
|
type: Directory
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.volumes }}
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{ toYaml . | indent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{ toYaml . | indent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.tolerations }}
|
{{- with .Values.tolerations }}
|
||||||
tolerations:
|
tolerations:
|
||||||
{{ toYaml . | indent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- with .Values.runtimeClassName }}
|
||||||
|
runtimeClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.priorityClassName }}
|
||||||
|
priorityClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.dnsConfig }}
|
||||||
|
dnsConfig:
|
||||||
|
{{- toYaml . | nindent 8}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.dnsPolicy }}
|
||||||
|
dnsPolicy: {{ .Values.dnsPolicy | quote }}
|
||||||
|
{{- else if or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork }}
|
||||||
|
dnsPolicy: ClusterFirstWithHostNet
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{{- if .Values.httpRoute.enabled }}
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.httpRoute.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.httpRoute.parentRefs }}
|
||||||
|
parentRefs:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.httpRoute.hostnames }}
|
||||||
|
hostnames:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.httpRoute.rules }}
|
||||||
|
- matches:
|
||||||
|
{{- range .matches }}
|
||||||
|
- path:
|
||||||
|
type: {{ .path.type }}
|
||||||
|
value: {{ .path.value }}
|
||||||
|
{{- end }}
|
||||||
|
backendRefs:
|
||||||
|
- name: {{ include "jellyfin.fullname" $ }}
|
||||||
|
port: {{ $.Values.service.port }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,50 +1,43 @@
|
|||||||
{{- if .Values.ingress.enabled -}}
|
{{- if .Values.ingress.enabled }}
|
||||||
{{- $fullName := include "jellyfin.fullname" . -}}
|
|
||||||
{{- $servicePort := .Values.service.port -}}
|
|
||||||
{{- $ingressPath := .Values.ingress.path -}}
|
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ $fullName }}
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
{{- with .Values.ingress.annotations }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
{{- with .Values.ingress.labels }}
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.ingress.annotations }}
|
|
||||||
annotations:
|
annotations:
|
||||||
{{ toYaml . | indent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
{{- if or (.Capabilities.APIVersions.Has "networking.k8s.io/v1/IngressClass") (.Capabilities.APIVersions.Has "networking.k8s.io/v1beta1/IngressClass") }}
|
{{- with .Values.ingress.className }}
|
||||||
{{- if .Values.ingress.ingressClassName }}
|
ingressClassName: {{ . }}
|
||||||
ingressClassName: {{ .Values.ingress.ingressClassName }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- if .Values.ingress.tls }}
|
||||||
{{- if .Values.ingress.tls }}
|
|
||||||
tls:
|
tls:
|
||||||
{{- range .Values.ingress.tls }}
|
{{- range .Values.ingress.tls }}
|
||||||
- hosts:
|
- hosts:
|
||||||
{{- range .hosts }}
|
{{- range .hosts }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
secretName: {{ .secretName }}
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
|
||||||
rules:
|
rules:
|
||||||
{{- range .Values.ingress.hosts }}
|
{{- range .Values.ingress.hosts }}
|
||||||
- host: {{ . | quote }}
|
- host: {{ .host | quote }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- pathType: Prefix
|
{{- range .paths }}
|
||||||
path: {{ $ingressPath }}
|
- path: {{ .path }}
|
||||||
|
{{- with .pathType }}
|
||||||
|
pathType: {{ . }}
|
||||||
|
{{- end }}
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ $fullName }}
|
name: {{ include "jellyfin.fullname" $ }}
|
||||||
port:
|
port:
|
||||||
name: http
|
number: {{ $.Values.service.port }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
{{- if and .Values.persistence.media.enabled (not .Values.persistence.media.existingClaim) }}
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: {{ template "jellyfin.fullname" . }}-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
|
||||||
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- {{ .Values.persistence.media.accessMode | quote }}
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: {{ .Values.persistence.media.size | quote }}
|
|
||||||
{{- if .Values.persistence.media.storageClass }}
|
|
||||||
{{- if (eq "-" .Values.persistence.media.storageClass) }}
|
|
||||||
storageClassName: ""
|
|
||||||
{{- else }}
|
|
||||||
storageClassName: "{{ .Values.persistence.media.storageClass }}"
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end -}}
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
{{- if .Values.networkPolicy.enabled }}
|
||||||
|
{{- if or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork }}
|
||||||
|
{{- fail "NetworkPolicy cannot be enabled when hostNetwork is enabled (DLNA mode or podPrivileges.hostNetwork). NetworkPolicy does not apply to pods using hostNetwork. Please set networkPolicy.enabled=false or disable hostNetwork." }}
|
||||||
|
{{- end }}
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "jellyfin.selectorLabels" . | nindent 6 }}
|
||||||
|
policyTypes:
|
||||||
|
{{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }}
|
||||||
|
|
||||||
|
{{- if has "Ingress" .Values.networkPolicy.policyTypes }}
|
||||||
|
ingress:
|
||||||
|
# Allow traffic to Jellyfin HTTP port
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: http
|
||||||
|
{{- if not .Values.networkPolicy.ingress.allowExternal }}
|
||||||
|
from:
|
||||||
|
{{- if or .Values.networkPolicy.ingress.podSelector .Values.networkPolicy.ingress.namespaceSelector }}
|
||||||
|
- podSelector:
|
||||||
|
{{- toYaml .Values.networkPolicy.ingress.podSelector | nindent 12 }}
|
||||||
|
{{- if .Values.networkPolicy.ingress.namespaceSelector }}
|
||||||
|
namespaceSelector:
|
||||||
|
{{- toYaml .Values.networkPolicy.ingress.namespaceSelector | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }}
|
||||||
|
# Allow Prometheus metrics scraping
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.metrics.serviceMonitor.port }}
|
||||||
|
from:
|
||||||
|
- podSelector:
|
||||||
|
{{- toYaml .Values.networkPolicy.metrics.podSelector | nindent 12 }}
|
||||||
|
{{- if .Values.networkPolicy.metrics.namespace }}
|
||||||
|
namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ .Values.networkPolicy.metrics.namespace }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- with .Values.networkPolicy.ingress.customRules }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if has "Egress" .Values.networkPolicy.policyTypes }}
|
||||||
|
egress:
|
||||||
|
{{- if .Values.networkPolicy.egress.allowDNS }}
|
||||||
|
# Allow DNS resolution (required for Jellyfin operation)
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ .Values.networkPolicy.egress.dnsNamespace }}
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
{{- toYaml .Values.networkPolicy.egress.dnsPodSelector | nindent 14 }}
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Values.networkPolicy.egress.allowAllEgress }}
|
||||||
|
# Allow all egress traffic (metadata providers, subtitles, images, etc.)
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
{{- else }}
|
||||||
|
{{- if .Values.networkPolicy.egress.restrictedEgress.allowMetadata }}
|
||||||
|
# Allow HTTPS for metadata providers (TMDB, TheTVDB, OpenSubtitles, etc.)
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Values.networkPolicy.egress.restrictedEgress.allowInCluster }}
|
||||||
|
# Allow pod-to-pod communication within the cluster
|
||||||
|
- to:
|
||||||
|
- podSelector: {}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- range .Values.networkPolicy.egress.restrictedEgress.allowedCIDRs }}
|
||||||
|
# Custom CIDR egress rule
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- with .Values.networkPolicy.egress.customRules }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{{- if and .Values.persistence.config.enabled (not .Values.persistence.config.existingClaim) }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}-config
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.persistence.config.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.config.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.config.size | quote }}
|
||||||
|
{{- with .Values.persistence.config.storageClass }}
|
||||||
|
storageClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.persistence.media.enabled (eq .Values.persistence.media.type "pvc") (not .Values.persistence.media.existingClaim) }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}-media
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.persistence.media.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.media.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.media.size | quote }}
|
||||||
|
{{- with .Values.persistence.media.storageClass }}
|
||||||
|
storageClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "pvc") (not .Values.persistence.cache.existingClaim) }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}-cache
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.persistence.cache.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.cache.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.cache.size | quote }}
|
||||||
|
{{- with .Values.persistence.cache.storageClass }}
|
||||||
|
storageClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,52 +1,58 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ template "jellyfin.fullname" . }}
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
helm.sh/chart: {{ include "jellyfin.chart" . }}
|
{{- with .Values.service.labels }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
{{- toYaml . | nindent 4 }}
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
{{- end }}
|
||||||
{{- if .Values.service.labels }}
|
{{- with .Values.service.annotations }}
|
||||||
{{ toYaml .Values.service.labels | indent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.service.annotations }}
|
|
||||||
annotations:
|
annotations:
|
||||||
{{ toYaml . | indent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
{{- if (or (eq .Values.service.type "ClusterIP") (empty .Values.service.type)) }}
|
|
||||||
type: ClusterIP
|
|
||||||
{{- if .Values.service.clusterIP }}
|
|
||||||
clusterIP: {{ .Values.service.clusterIP }}
|
|
||||||
{{end}}
|
|
||||||
{{- else if eq .Values.service.type "LoadBalancer" }}
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
{{- if .Values.service.loadBalancerIP }}
|
|
||||||
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.service.loadBalancerSourceRanges }}
|
spec:
|
||||||
|
{{- if (or (eq .Values.service.type "ClusterIP") (empty .Values.service.type)) }}
|
||||||
|
type: ClusterIP
|
||||||
|
{{- with .Values.service.clusterIP }}
|
||||||
|
clusterIP: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if eq .Values.service.type "LoadBalancer" }}
|
||||||
|
type: LoadBalancer
|
||||||
|
{{- with .Values.service.loadBalancerIP }}
|
||||||
|
loadBalancerIP: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.loadBalancerClass }}
|
||||||
|
loadBalancerClass: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.loadBalancerSourceRanges }}
|
||||||
loadBalancerSourceRanges:
|
loadBalancerSourceRanges:
|
||||||
{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
{{- end -}}
|
{{- end }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
type: {{ .Values.service.type }}
|
type: {{ .Values.service.type }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.service.externalIPs }}
|
{{- if .Values.service.ipFamilyPolicy }}
|
||||||
|
ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.service.ipFamilies }}
|
||||||
|
ipFamilies:
|
||||||
|
{{- toYaml .Values.service.ipFamilies | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.externalIPs }}
|
||||||
externalIPs:
|
externalIPs:
|
||||||
{{ toYaml .Values.service.externalIPs | indent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.service.externalTrafficPolicy }}
|
{{- with .Values.service.externalTrafficPolicy }}
|
||||||
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
|
externalTrafficPolicy: {{ . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: {{ .Values.service.name | default "http" }}
|
||||||
port: {{ .Values.service.port }}
|
port: {{ .Values.service.port }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
targetPort: http
|
targetPort: http
|
||||||
{{ if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
|
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
|
||||||
nodePort: {{.Values.service.nodePort}}
|
nodePort: {{ .Values.service.nodePort }}
|
||||||
{{ end }}
|
{{- end }}
|
||||||
selector:
|
selector:
|
||||||
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
{{- include "jellyfin.selectorLabels" . | nindent 4 }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{{- if and .Values.metrics.serviceMonitor.enabled .Values.metrics.enabled }}
|
||||||
|
---
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.fullname" . }}
|
||||||
|
{{- if .Values.metrics.serviceMonitor.namespace }}
|
||||||
|
namespace: {{ tpl .Values.metrics.serviceMonitor.namespace . }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.labels }}
|
||||||
|
{{- tpl (toYaml . | nindent 4) $ }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
endpoints:
|
||||||
|
- port: {{ .Values.service.name | default "http" }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.interval }}
|
||||||
|
interval: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.scrapeTimeout }}
|
||||||
|
scrapeTimeout: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
honorLabels: true
|
||||||
|
path: {{ .Values.metrics.serviceMonitor.path }}
|
||||||
|
scheme: {{ .Values.metrics.serviceMonitor.scheme }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.tlsConfig }}
|
||||||
|
tlsConfig:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.relabelings }}
|
||||||
|
relabelings:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.metricRelabelings }}
|
||||||
|
metricRelabelings:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
jobLabel: "{{ .Release.Name }}"
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- with .Values.metrics.serviceMonitor.targetLabels }}
|
||||||
|
targetLabels:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "jellyfin.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "jellyfin.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
suite: test deployment image configuration
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# IMAGE REPOSITORY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use default image repository
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: "^docker\\.io/jellyfin/jellyfin:"
|
||||||
|
|
||||||
|
- it: should use custom image repository
|
||||||
|
set:
|
||||||
|
image.repository: ghcr.io/jellyfin/jellyfin
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: "^ghcr\\.io/jellyfin/jellyfin:"
|
||||||
|
|
||||||
|
- it: should use private registry
|
||||||
|
set:
|
||||||
|
image.repository: registry.example.com/jellyfin/jellyfin
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: "^registry\\.example\\.com/jellyfin/jellyfin:"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# IMAGE TAG
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use Chart.AppVersion as default tag
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: ":[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
|
|
||||||
|
- it: should use custom tag when specified
|
||||||
|
set:
|
||||||
|
image.tag: 10.8.13
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: docker.io/jellyfin/jellyfin:10.8.13
|
||||||
|
|
||||||
|
- it: should support latest tag
|
||||||
|
set:
|
||||||
|
image.tag: latest
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: docker.io/jellyfin/jellyfin:latest
|
||||||
|
|
||||||
|
- it: should support beta tags
|
||||||
|
set:
|
||||||
|
image.tag: 10.9.0-beta1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: docker.io/jellyfin/jellyfin:10.9.0-beta1
|
||||||
|
|
||||||
|
- it: should support sha256 digest
|
||||||
|
set:
|
||||||
|
image.tag: sha256:abc123def456
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: docker.io/jellyfin/jellyfin:sha256:abc123def456
|
||||||
|
|
||||||
|
- it: should combine custom repository and tag
|
||||||
|
set:
|
||||||
|
image.repository: ghcr.io/custom/jellyfin
|
||||||
|
image.tag: custom-1.0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: ghcr.io/custom/jellyfin:custom-1.0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# IMAGE PULL POLICY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use IfNotPresent as default pullPolicy
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: IfNotPresent
|
||||||
|
|
||||||
|
- it: should set Always pullPolicy
|
||||||
|
set:
|
||||||
|
image.pullPolicy: Always
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: Always
|
||||||
|
|
||||||
|
- it: should set Never pullPolicy
|
||||||
|
set:
|
||||||
|
image.pullPolicy: Never
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: Never
|
||||||
|
|
||||||
|
- it: should explicitly set IfNotPresent pullPolicy
|
||||||
|
set:
|
||||||
|
image.pullPolicy: IfNotPresent
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: IfNotPresent
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# IMAGE PULL SECRETS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have imagePullSecrets by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
|
||||||
|
- it: should add single imagePullSecret
|
||||||
|
set:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: regcred
|
||||||
|
|
||||||
|
- it: should add multiple imagePullSecrets
|
||||||
|
set:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred-1
|
||||||
|
- name: regcred-2
|
||||||
|
- name: regcred-3
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
count: 3
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: regcred-1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[1].name
|
||||||
|
value: regcred-2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[2].name
|
||||||
|
value: regcred-3
|
||||||
|
|
||||||
|
- it: should support Docker Hub secrets
|
||||||
|
set:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: dockerhub-secret
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: dockerhub-secret
|
||||||
|
|
||||||
|
- it: should support GitHub Container Registry secrets
|
||||||
|
set:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-secret
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: ghcr-secret
|
||||||
|
|
||||||
|
- it: should support private registry secrets
|
||||||
|
set:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: private-registry-secret
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: private-registry-secret
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty imagePullSecrets array
|
||||||
|
set:
|
||||||
|
imagePullSecrets: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
|
||||||
|
- it: should handle empty tag (use default)
|
||||||
|
set:
|
||||||
|
image.tag: ""
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: ":[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure for private registry with authentication
|
||||||
|
set:
|
||||||
|
image.repository: registry.company.com/jellyfin/jellyfin
|
||||||
|
image.tag: 10.8.13-enterprise
|
||||||
|
image.pullPolicy: Always
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: company-registry-secret
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: registry.company.com/jellyfin/jellyfin:10.8.13-enterprise
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: Always
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: company-registry-secret
|
||||||
|
|
||||||
|
- it: should configure for development with latest tag
|
||||||
|
set:
|
||||||
|
image.repository: jellyfin/jellyfin
|
||||||
|
image.tag: latest
|
||||||
|
image.pullPolicy: Always
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: jellyfin/jellyfin:latest
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: Always
|
||||||
|
|
||||||
|
- it: should configure for multiple registry secrets
|
||||||
|
set:
|
||||||
|
image.repository: multi-registry.example.com/jellyfin
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: registry-1-secret
|
||||||
|
- name: registry-2-secret
|
||||||
|
- name: backup-registry-secret
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
- it: should configure for air-gapped environment
|
||||||
|
set:
|
||||||
|
image.repository: internal-registry.local/jellyfin/jellyfin
|
||||||
|
image.tag: 10.8.13-airgap
|
||||||
|
image.pullPolicy: IfNotPresent
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: internal-registry-creds
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: internal-registry.local/jellyfin/jellyfin:10.8.13-airgap
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: IfNotPresent
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always have image specified
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
|
||||||
|
- it: should always have pullPolicy specified
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
|
||||||
|
- it: should construct image correctly with defaults
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: '^docker\.io/jellyfin/jellyfin:[0-9]+\.[0-9]+\.[0-9]+$'
|
||||||
|
|
||||||
|
- it: should not have imagePullSecrets when not specified
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
|
||||||
|
# Verify image format with various combinations
|
||||||
|
- it: should format image with repository without registry
|
||||||
|
set:
|
||||||
|
image.repository: jellyfin/jellyfin-custom
|
||||||
|
image.tag: v1.0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: jellyfin/jellyfin-custom:v1.0
|
||||||
|
|
||||||
|
- it: should format image with full registry path
|
||||||
|
set:
|
||||||
|
image.repository: docker.io/jellyfin/jellyfin
|
||||||
|
image.tag: stable
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: docker.io/jellyfin/jellyfin:stable
|
||||||
|
|
||||||
|
- it: should format image with port in registry
|
||||||
|
set:
|
||||||
|
image.repository: registry.example.com:5000/jellyfin/jellyfin
|
||||||
|
image.tag: custom
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: registry.example.com:5000/jellyfin/jellyfin:custom
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
suite: test jellyfin-specific deployment configuration
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# DLNA AND HOST NETWORK
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not enable DLNA by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should enable hostNetwork when DLNA enabled
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should set dnsPolicy to ClusterFirstWithHostNet when DLNA enabled
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
|
||||||
|
- it: should enable hostNetwork when explicitly set
|
||||||
|
set:
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should enable DLNA with all required settings
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# METRICS LIFECYCLE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have lifecycle by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle
|
||||||
|
|
||||||
|
- it: should add postStart hook when metrics enabled
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle.postStart
|
||||||
|
|
||||||
|
- it: should execute metrics setup script in postStart
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle.postStart.exec.command[0]
|
||||||
|
value: /bin/sh
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle.postStart.exec.command
|
||||||
|
content: -c
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMMAND AND ARGS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not override command by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].command
|
||||||
|
|
||||||
|
- it: should set custom command when specified
|
||||||
|
set:
|
||||||
|
jellyfin.command: ['/custom/jellyfin']
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].command[0]
|
||||||
|
value: /custom/jellyfin
|
||||||
|
|
||||||
|
- it: should set complex command
|
||||||
|
set:
|
||||||
|
jellyfin.command: ['/bin/sh', '-c']
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].command
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].command[0]
|
||||||
|
value: /bin/sh
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].command[1]
|
||||||
|
value: -c
|
||||||
|
|
||||||
|
- it: should not override args by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
|
||||||
|
- it: should set custom args when specified
|
||||||
|
set:
|
||||||
|
jellyfin.args: ['--debug']
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
content: --debug
|
||||||
|
|
||||||
|
- it: should set multiple args
|
||||||
|
set:
|
||||||
|
jellyfin.args: ['--debug', '--verbose', '--log-level=trace']
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
count: 3
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
content: --debug
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
content: --log-level=trace
|
||||||
|
|
||||||
|
- it: should set both command and args
|
||||||
|
set:
|
||||||
|
jellyfin.command: ['/bin/sh', '-c']
|
||||||
|
jellyfin.args: ['exec /jellyfin/jellyfin --debug']
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].command
|
||||||
|
count: 2
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ENVIRONMENT VARIABLES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have custom env by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
|
||||||
|
- it: should set single environment variable
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_LOG_LEVEL
|
||||||
|
value: Debug
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].name
|
||||||
|
value: JELLYFIN_LOG_LEVEL
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].value
|
||||||
|
value: Debug
|
||||||
|
|
||||||
|
- it: should set multiple environment variables
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_LOG_LEVEL
|
||||||
|
value: Debug
|
||||||
|
- name: JELLYFIN_CACHE_DIR
|
||||||
|
value: /cache
|
||||||
|
- name: JELLYFIN_DATA_DIR
|
||||||
|
value: /config
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
- it: should support env from Secret
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: jellyfin-secrets
|
||||||
|
key: api-key
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].name
|
||||||
|
value: API_KEY
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.name
|
||||||
|
value: jellyfin-secrets
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.key
|
||||||
|
value: api-key
|
||||||
|
|
||||||
|
- it: should support env from ConfigMap
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: CONFIG_VALUE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: jellyfin-config
|
||||||
|
key: config-value
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].valueFrom.configMapKeyRef.name
|
||||||
|
value: jellyfin-config
|
||||||
|
|
||||||
|
- it: should support env from fieldRef
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].valueFrom.fieldRef.fieldPath
|
||||||
|
value: metadata.name
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[1].valueFrom.fieldRef.fieldPath
|
||||||
|
value: metadata.namespace
|
||||||
|
|
||||||
|
- it: should support env from resourceFieldRef
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: MEMORY_LIMIT
|
||||||
|
valueFrom:
|
||||||
|
resourceFieldRef:
|
||||||
|
containerName: jellyfin
|
||||||
|
resource: limits.memory
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].valueFrom.resourceFieldRef.resource
|
||||||
|
value: limits.memory
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CONTAINER PORTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should expose default http port
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].ports
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].name
|
||||||
|
value: http
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].containerPort
|
||||||
|
value: 8096
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].protocol
|
||||||
|
value: TCP
|
||||||
|
|
||||||
|
- it: should keep containerPort 8096 regardless of service port
|
||||||
|
set:
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].containerPort
|
||||||
|
value: 8096
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty command array
|
||||||
|
set:
|
||||||
|
jellyfin.command: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].command
|
||||||
|
|
||||||
|
- it: should handle empty args array
|
||||||
|
set:
|
||||||
|
jellyfin.args: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
|
||||||
|
- it: should handle empty env array
|
||||||
|
set:
|
||||||
|
jellyfin.env: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
|
||||||
|
# Mix of different env types
|
||||||
|
- it: should support mix of env value types
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: DIRECT_VALUE
|
||||||
|
value: test
|
||||||
|
- name: SECRET_VALUE
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: my-secret
|
||||||
|
key: secret-key
|
||||||
|
- name: CONFIG_VALUE
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: my-config
|
||||||
|
key: config-key
|
||||||
|
- name: POD_IP
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: status.podIP
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
count: 4
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].env[0].value
|
||||||
|
value: test
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].env[1].valueFrom.secretKeyRef
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].env[2].valueFrom.configMapKeyRef
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].env[3].valueFrom.fieldRef
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure for DLNA with custom env
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: http://jellyfin.local:8096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should configure with metrics and custom command
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
jellyfin.command: ['/custom/jellyfin']
|
||||||
|
jellyfin.args: ['--enable-metrics']
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle.postStart
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].command[0]
|
||||||
|
value: /custom/jellyfin
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
content: --enable-metrics
|
||||||
|
|
||||||
|
- it: should configure complete custom jellyfin setup
|
||||||
|
set:
|
||||||
|
jellyfin.command: ['/jellyfin/jellyfin']
|
||||||
|
jellyfin.args: ['--service']
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_LOG_LEVEL
|
||||||
|
value: Information
|
||||||
|
- name: JELLYFIN_CACHE_DIR
|
||||||
|
value: /cache
|
||||||
|
- name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: https://jellyfin.example.com
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: jellyfin-db
|
||||||
|
key: password
|
||||||
|
service.port: 8096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].command[0]
|
||||||
|
value: /jellyfin/jellyfin
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].args
|
||||||
|
content: --service
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
count: 4
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].containerPort
|
||||||
|
value: 8096
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always have http port exposed
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].ports
|
||||||
|
content:
|
||||||
|
name: http
|
||||||
|
containerPort: 8096
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
- it: should always use TCP protocol for http port
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].protocol
|
||||||
|
value: TCP
|
||||||
|
|
||||||
|
- it: should not enable hostNetwork by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should not have metrics lifecycle without metrics.enabled
|
||||||
|
set:
|
||||||
|
metrics.enabled: false
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
suite: test deployment metadata and strategy
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# NAMES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default name
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should use nameOverride
|
||||||
|
set:
|
||||||
|
nameOverride: custom-name
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-custom-name
|
||||||
|
|
||||||
|
- it: should use fullnameOverride
|
||||||
|
set:
|
||||||
|
fullnameOverride: completely-custom
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: completely-custom
|
||||||
|
|
||||||
|
- it: should prefer fullnameOverride over nameOverride
|
||||||
|
set:
|
||||||
|
nameOverride: ignored
|
||||||
|
fullnameOverride: used-name
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: used-name
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT LABELS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default labels
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["app.kubernetes.io/version"]
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["helm.sh/chart"]
|
||||||
|
|
||||||
|
- it: should propagate labels to pod template
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should use labels in selector
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT ANNOTATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have deployment annotations by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should add deployment annotations when specified
|
||||||
|
set:
|
||||||
|
deploymentAnnotations:
|
||||||
|
deployment.kubernetes.io/revision: "1"
|
||||||
|
custom-annotation: custom-value
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["deployment.kubernetes.io/revision"]
|
||||||
|
value: "1"
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.custom-annotation
|
||||||
|
value: custom-value
|
||||||
|
|
||||||
|
- it: should support multiple deployment annotations
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
deploymentAnnotations:
|
||||||
|
annotation1: value1
|
||||||
|
annotation2: value2
|
||||||
|
annotation3: value3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation1
|
||||||
|
value: value1
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation2
|
||||||
|
value: value2
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation3
|
||||||
|
value: value3
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# POD ANNOTATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have pod annotations by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.metadata.annotations
|
||||||
|
|
||||||
|
- it: should add pod annotations when specified
|
||||||
|
set:
|
||||||
|
podAnnotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8096"
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["prometheus.io/scrape"]
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["prometheus.io/port"]
|
||||||
|
value: "8096"
|
||||||
|
|
||||||
|
- it: should support multiple pod annotations
|
||||||
|
set:
|
||||||
|
podAnnotations:
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/role: jellyfin
|
||||||
|
custom-annotation: custom-value
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["vault.hashicorp.com/agent-inject"]
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["vault.hashicorp.com/role"]
|
||||||
|
value: jellyfin
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# POD LABELS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default pod labels
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should add custom pod labels when specified
|
||||||
|
set:
|
||||||
|
podLabels:
|
||||||
|
environment: production
|
||||||
|
team: platform
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.environment
|
||||||
|
value: production
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.team
|
||||||
|
value: platform
|
||||||
|
|
||||||
|
- it: should merge custom pod labels with default labels
|
||||||
|
set:
|
||||||
|
podLabels:
|
||||||
|
custom: label
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.custom
|
||||||
|
value: label
|
||||||
|
|
||||||
|
- it: should support multiple custom pod labels
|
||||||
|
set:
|
||||||
|
podLabels:
|
||||||
|
label1: value1
|
||||||
|
label2: value2
|
||||||
|
label3: value3
|
||||||
|
label4: value4
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.label1
|
||||||
|
value: value1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.label4
|
||||||
|
value: value4
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT STRATEGY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use RollingUpdate strategy by default
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: RollingUpdate
|
||||||
|
|
||||||
|
- it: should set Recreate strategy
|
||||||
|
set:
|
||||||
|
deploymentStrategy.type: Recreate
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: Recreate
|
||||||
|
|
||||||
|
- it: should explicitly set RollingUpdate strategy
|
||||||
|
set:
|
||||||
|
deploymentStrategy.type: RollingUpdate
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: RollingUpdate
|
||||||
|
|
||||||
|
- it: should not have rollingUpdate params with Recreate strategy
|
||||||
|
set:
|
||||||
|
deploymentStrategy.type: Recreate
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.strategy.rollingUpdate
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REVISION HISTORY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default revisionHistoryLimit of 3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
- it: should set custom revisionHistoryLimit
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 10
|
||||||
|
|
||||||
|
- it: should set revisionHistoryLimit to 0
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REPLICA COUNT
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default replicaCount of 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should set custom replicaCount
|
||||||
|
set:
|
||||||
|
replicaCount: 2
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 2
|
||||||
|
|
||||||
|
- it: should support replicaCount of 3
|
||||||
|
set:
|
||||||
|
replicaCount: 3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
# Note: Jellyfin doesn't support horizontal scaling well, but chart allows it
|
||||||
|
# Users should be warned via NOTES.txt if replicaCount > 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty podAnnotations object
|
||||||
|
set:
|
||||||
|
podAnnotations: {}
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.metadata.annotations
|
||||||
|
|
||||||
|
- it: should handle empty podLabels object
|
||||||
|
set:
|
||||||
|
podLabels: {}
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.metadata.labels["app.kubernetes.io/name"]
|
||||||
|
|
||||||
|
- it: should handle empty deploymentAnnotations object
|
||||||
|
set:
|
||||||
|
deploymentAnnotations: {}
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should handle empty nameOverride
|
||||||
|
set:
|
||||||
|
nameOverride: ""
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure production deployment with full metadata
|
||||||
|
set:
|
||||||
|
fullnameOverride: jellyfin-prod
|
||||||
|
deploymentAnnotations:
|
||||||
|
deployment.kubernetes.io/revision: "5"
|
||||||
|
ci.build.number: "1234"
|
||||||
|
podAnnotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8096"
|
||||||
|
podLabels:
|
||||||
|
environment: production
|
||||||
|
team: media
|
||||||
|
version: v10.8.13
|
||||||
|
deploymentStrategy.type: RollingUpdate
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
replicaCount: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: jellyfin-prod
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["deployment.kubernetes.io/revision"]
|
||||||
|
value: "5"
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["prometheus.io/scrape"]
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.environment
|
||||||
|
value: production
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: RollingUpdate
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 10
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should configure development deployment with Recreate strategy
|
||||||
|
set:
|
||||||
|
nameOverride: jellyfin-dev
|
||||||
|
podLabels:
|
||||||
|
environment: development
|
||||||
|
deploymentStrategy.type: Recreate
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-dev
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.labels.environment
|
||||||
|
value: development
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: Recreate
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 5
|
||||||
|
|
||||||
|
- it: should configure with vault injection annotations
|
||||||
|
set:
|
||||||
|
podAnnotations:
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-inject-secret-jellyfin: secret/data/jellyfin
|
||||||
|
vault.hashicorp.com/role: jellyfin-role
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["vault.hashicorp.com/agent-inject"]
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: spec.template.metadata.annotations["vault.hashicorp.com/role"]
|
||||||
|
value: jellyfin-role
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always have standard Kubernetes labels
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["app.kubernetes.io/version"]
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["helm.sh/chart"]
|
||||||
|
|
||||||
|
- it: should always have matching selectors
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should always have spec.strategy defined
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.strategy
|
||||||
|
|
||||||
|
- it: should always have spec.replicas defined
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.replicas
|
||||||
|
|
||||||
|
- it: should always have spec.revisionHistoryLimit defined
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
@@ -0,0 +1,523 @@
|
|||||||
|
suite: test deployment resources and volumes
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# RESOURCES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have resources by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].resources
|
||||||
|
|
||||||
|
- it: should set CPU requests
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.cpu
|
||||||
|
value: 100m
|
||||||
|
|
||||||
|
- it: should set memory requests
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 128Mi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.memory
|
||||||
|
value: 128Mi
|
||||||
|
|
||||||
|
- it: should set CPU limits
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits.cpu
|
||||||
|
value: 500m
|
||||||
|
|
||||||
|
- it: should set memory limits
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512Mi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits.memory
|
||||||
|
value: 512Mi
|
||||||
|
|
||||||
|
- it: should set both requests and limits
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1Gi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.cpu
|
||||||
|
value: 100m
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.memory
|
||||||
|
value: 256Mi
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits.cpu
|
||||||
|
value: 1000m
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits.memory
|
||||||
|
value: 1Gi
|
||||||
|
|
||||||
|
- it: should support GPU resources
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
nvidia.com/gpu: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits["nvidia.com/gpu"]
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should support ephemeral-storage
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
ephemeral-storage: 1Gi
|
||||||
|
limits:
|
||||||
|
ephemeral-storage: 2Gi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.ephemeral-storage
|
||||||
|
value: 1Gi
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.limits.ephemeral-storage
|
||||||
|
value: 2Gi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EXTRA VOLUMES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have extra volumes by default
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 3 # config, media, cache only
|
||||||
|
|
||||||
|
- it: should add extraVolumes when specified
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: extra-storage
|
||||||
|
emptyDir: {}
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 4
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: extra-storage
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should add multiple extraVolumes
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: extra-1
|
||||||
|
emptyDir: {}
|
||||||
|
- name: extra-2
|
||||||
|
secret:
|
||||||
|
secretName: my-secret
|
||||||
|
- name: extra-3
|
||||||
|
configMap:
|
||||||
|
name: my-configmap
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 6
|
||||||
|
|
||||||
|
- it: should support secret volume
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: secret-vol
|
||||||
|
secret:
|
||||||
|
secretName: my-secret
|
||||||
|
optional: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: secret-vol
|
||||||
|
secret:
|
||||||
|
secretName: my-secret
|
||||||
|
optional: false
|
||||||
|
|
||||||
|
- it: should support configMap volume
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: config-vol
|
||||||
|
configMap:
|
||||||
|
name: my-config
|
||||||
|
items:
|
||||||
|
- key: config.yaml
|
||||||
|
path: config.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config-vol
|
||||||
|
configMap:
|
||||||
|
name: my-config
|
||||||
|
items:
|
||||||
|
- key: config.yaml
|
||||||
|
path: config.yaml
|
||||||
|
|
||||||
|
- it: should support NFS volume
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: nfs-storage
|
||||||
|
nfs:
|
||||||
|
server: nfs-server.example.com
|
||||||
|
path: /exported/path
|
||||||
|
readOnly: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: nfs-storage
|
||||||
|
nfs:
|
||||||
|
server: nfs-server.example.com
|
||||||
|
path: /exported/path
|
||||||
|
readOnly: false
|
||||||
|
|
||||||
|
- it: should support PVC volume
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: pvc-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: existing-pvc
|
||||||
|
readOnly: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: pvc-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: existing-pvc
|
||||||
|
readOnly: false
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VOLUME MOUNTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have extra volume mounts by default
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 3 # config, media, cache only
|
||||||
|
|
||||||
|
- it: should add volumeMounts when specified
|
||||||
|
set:
|
||||||
|
volumeMounts:
|
||||||
|
- name: extra-storage
|
||||||
|
mountPath: /extra
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 4
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: extra-storage
|
||||||
|
mountPath: /extra
|
||||||
|
|
||||||
|
- it: should add multiple volumeMounts
|
||||||
|
set:
|
||||||
|
volumeMounts:
|
||||||
|
- name: extra-1
|
||||||
|
mountPath: /extra1
|
||||||
|
- name: extra-2
|
||||||
|
mountPath: /extra2
|
||||||
|
readOnly: true
|
||||||
|
- name: extra-3
|
||||||
|
mountPath: /extra3
|
||||||
|
subPath: subdir
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 6
|
||||||
|
|
||||||
|
- it: should support volumeMount with all options
|
||||||
|
set:
|
||||||
|
volumeMounts:
|
||||||
|
- name: complete-mount
|
||||||
|
mountPath: /complete
|
||||||
|
subPath: subdir
|
||||||
|
readOnly: true
|
||||||
|
mountPropagation: HostToContainer
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: complete-mount
|
||||||
|
mountPath: /complete
|
||||||
|
subPath: subdir
|
||||||
|
readOnly: true
|
||||||
|
mountPropagation: HostToContainer
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EXTRA CONTAINERS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have extra containers by default
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should add extraContainers when specified
|
||||||
|
set:
|
||||||
|
extraContainers:
|
||||||
|
- name: sidecar
|
||||||
|
image: nginx:latest
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].name
|
||||||
|
value: sidecar
|
||||||
|
|
||||||
|
- it: should add multiple extraContainers
|
||||||
|
set:
|
||||||
|
extraContainers:
|
||||||
|
- name: sidecar-1
|
||||||
|
image: nginx:latest
|
||||||
|
- name: sidecar-2
|
||||||
|
image: busybox:latest
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
- it: should preserve full sidecar container spec
|
||||||
|
set:
|
||||||
|
extraContainers:
|
||||||
|
- name: metrics-exporter
|
||||||
|
image: prom/node-exporter:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 9100
|
||||||
|
name: metrics
|
||||||
|
env:
|
||||||
|
- name: EXPORT_INTERVAL
|
||||||
|
value: "30s"
|
||||||
|
volumeMounts:
|
||||||
|
- name: proc
|
||||||
|
mountPath: /host/proc
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 64Mi
|
||||||
|
volumes:
|
||||||
|
- name: proc
|
||||||
|
hostPath:
|
||||||
|
path: /proc
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].name
|
||||||
|
value: metrics-exporter
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].image
|
||||||
|
value: prom/node-exporter:latest
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].ports[0].containerPort
|
||||||
|
value: 9100
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].env[0].name
|
||||||
|
value: EXPORT_INTERVAL
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].volumeMounts[0].mountPath
|
||||||
|
value: /host/proc
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].resources.requests.cpu
|
||||||
|
value: 50m
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty resources object
|
||||||
|
set:
|
||||||
|
resources: {}
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].resources
|
||||||
|
|
||||||
|
- it: should handle empty volumes array
|
||||||
|
set:
|
||||||
|
volumes: []
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 3 # Still have default volumes
|
||||||
|
|
||||||
|
- it: should handle empty volumeMounts array
|
||||||
|
set:
|
||||||
|
volumeMounts: []
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 3 # Still have default mounts
|
||||||
|
|
||||||
|
- it: should handle empty extraContainers array
|
||||||
|
set:
|
||||||
|
extraContainers: []
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# Combine volumes and volumeMounts
|
||||||
|
- it: should combine extraVolumes and volumeMounts correctly
|
||||||
|
set:
|
||||||
|
volumes:
|
||||||
|
- name: shared-data
|
||||||
|
emptyDir: {}
|
||||||
|
volumeMounts:
|
||||||
|
- name: shared-data
|
||||||
|
mountPath: /shared
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: shared-data
|
||||||
|
emptyDir: {}
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: shared-data
|
||||||
|
mountPath: /shared
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure complete resource-constrained setup
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 4Gi
|
||||||
|
volumes:
|
||||||
|
- name: transcode-cache
|
||||||
|
emptyDir:
|
||||||
|
medium: Memory
|
||||||
|
sizeLimit: 8Gi
|
||||||
|
- name: backup
|
||||||
|
nfs:
|
||||||
|
server: backup.example.com
|
||||||
|
path: /backups/jellyfin
|
||||||
|
volumeMounts:
|
||||||
|
- name: transcode-cache
|
||||||
|
mountPath: /transcode
|
||||||
|
- name: backup
|
||||||
|
mountPath: /backup
|
||||||
|
readOnly: false
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].resources
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 5 # config, media, cache + 2 extra
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 5
|
||||||
|
|
||||||
|
- it: should configure sidecar with shared volume
|
||||||
|
set:
|
||||||
|
extraContainers:
|
||||||
|
- name: log-forwarder
|
||||||
|
image: fluent/fluent-bit:latest
|
||||||
|
volumeMounts:
|
||||||
|
- name: logs
|
||||||
|
mountPath: /fluent-bit/logs
|
||||||
|
volumes:
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
|
volumeMounts:
|
||||||
|
- name: logs
|
||||||
|
mountPath: /logs
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].volumeMounts[0].name
|
||||||
|
value: logs
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always have default volumes
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-media
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should always have default volume mounts
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
mountPath: /config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
mountPath: /media
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
mountPath: /cache
|
||||||
|
|
||||||
|
- it: should always have jellyfin as first container
|
||||||
|
set:
|
||||||
|
extraContainers:
|
||||||
|
- name: sidecar
|
||||||
|
image: nginx:latest
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].name
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[1].name
|
||||||
|
value: sidecar
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
suite: test deployment scheduling
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# NODE SELECTOR
|
||||||
|
- it: should not have nodeSelector by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.nodeSelector
|
||||||
|
|
||||||
|
- it: should set single nodeSelector
|
||||||
|
set:
|
||||||
|
nodeSelector:
|
||||||
|
disktype: ssd
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.nodeSelector.disktype
|
||||||
|
value: ssd
|
||||||
|
|
||||||
|
- it: should set multiple nodeSelectors
|
||||||
|
set:
|
||||||
|
nodeSelector:
|
||||||
|
disktype: ssd
|
||||||
|
zone: us-west1-a
|
||||||
|
instance-type: n1-standard-4
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.nodeSelector.disktype
|
||||||
|
value: ssd
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.nodeSelector.zone
|
||||||
|
value: us-west1-a
|
||||||
|
|
||||||
|
# TOLERATIONS
|
||||||
|
- it: should not have tolerations by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.tolerations
|
||||||
|
|
||||||
|
- it: should set toleration with Equal operator
|
||||||
|
set:
|
||||||
|
tolerations:
|
||||||
|
- key: key1
|
||||||
|
operator: Equal
|
||||||
|
value: value1
|
||||||
|
effect: NoSchedule
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.tolerations
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.tolerations[0].key
|
||||||
|
value: key1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.tolerations[0].operator
|
||||||
|
value: Equal
|
||||||
|
|
||||||
|
- it: should set toleration with Exists operator
|
||||||
|
set:
|
||||||
|
tolerations:
|
||||||
|
- key: special
|
||||||
|
operator: Exists
|
||||||
|
effect: NoExecute
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.tolerations[0].operator
|
||||||
|
value: Exists
|
||||||
|
|
||||||
|
- it: should support multiple tolerations
|
||||||
|
set:
|
||||||
|
tolerations:
|
||||||
|
- key: key1
|
||||||
|
operator: Equal
|
||||||
|
value: value1
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: key2
|
||||||
|
operator: Exists
|
||||||
|
effect: NoExecute
|
||||||
|
- key: key3
|
||||||
|
operator: Equal
|
||||||
|
value: value3
|
||||||
|
effect: PreferNoSchedule
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.tolerations
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
# AFFINITY
|
||||||
|
- it: should not have affinity by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.affinity
|
||||||
|
|
||||||
|
- it: should set nodeAffinity
|
||||||
|
set:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: disktype
|
||||||
|
operator: In
|
||||||
|
values: [ssd]
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.affinity.nodeAffinity
|
||||||
|
|
||||||
|
- it: should set podAffinity
|
||||||
|
set:
|
||||||
|
affinity:
|
||||||
|
podAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: cache
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.affinity.podAffinity
|
||||||
|
|
||||||
|
- it: should set podAntiAffinity
|
||||||
|
set:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: jellyfin
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.affinity.podAntiAffinity
|
||||||
|
|
||||||
|
# DNS CONFIGURATION
|
||||||
|
- it: should not have dnsPolicy set by default (Kubernetes uses ClusterFirst)
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
|
||||||
|
- it: should set custom dnsPolicy
|
||||||
|
set:
|
||||||
|
dnsPolicy: None
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: None
|
||||||
|
|
||||||
|
- it: should not have dnsConfig by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.dnsConfig
|
||||||
|
|
||||||
|
- it: should set dnsConfig when specified
|
||||||
|
set:
|
||||||
|
dnsConfig:
|
||||||
|
nameservers: [1.1.1.1, 8.8.8.8]
|
||||||
|
searches: [example.com]
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: "2"
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.dnsConfig.nameservers
|
||||||
|
content: 1.1.1.1
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.dnsConfig.searches
|
||||||
|
content: example.com
|
||||||
|
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
- it: should combine nodeSelector, tolerations, and affinity
|
||||||
|
set:
|
||||||
|
nodeSelector:
|
||||||
|
disktype: ssd
|
||||||
|
tolerations:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: jellyfin
|
||||||
|
effect: NoSchedule
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jellyfin
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.nodeSelector
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.tolerations
|
||||||
|
count: 1
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.affinity.podAntiAffinity
|
||||||
|
|
||||||
|
# REGRESSION
|
||||||
|
- it: should not set dnsPolicy when hostNetwork not enabled
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
suite: test deployment security contexts and privileges
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# POD SECURITY CONTEXT
|
||||||
|
- it: should not have podSecurityContext by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.securityContext
|
||||||
|
|
||||||
|
- it: should set podSecurityContext when specified
|
||||||
|
set:
|
||||||
|
podSecurityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.securityContext.fsGroup
|
||||||
|
value: 1000
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.securityContext.runAsUser
|
||||||
|
value: 1000
|
||||||
|
|
||||||
|
- it: should support runAsNonRoot
|
||||||
|
set:
|
||||||
|
podSecurityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.securityContext.runAsNonRoot
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# CONTAINER SECURITY CONTEXT
|
||||||
|
- it: should not have container securityContext by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].securityContext
|
||||||
|
|
||||||
|
- it: should set container securityContext when specified
|
||||||
|
set:
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation
|
||||||
|
value: false
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should support capabilities
|
||||||
|
set:
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
add: [NET_BIND_SERVICE]
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.capabilities.drop
|
||||||
|
content: ALL
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.capabilities.add
|
||||||
|
content: NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# POD PRIVILEGES
|
||||||
|
- it: should not enable hostNetwork by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should enable hostNetwork when specified
|
||||||
|
set:
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should set dnsPolicy to ClusterFirstWithHostNet when hostNetwork enabled
|
||||||
|
set:
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
|
||||||
|
- it: should not enable hostIPC by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostIPC
|
||||||
|
|
||||||
|
- it: should enable hostIPC when specified
|
||||||
|
set:
|
||||||
|
podPrivileges.hostIPC: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostIPC
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should not enable hostPID by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostPID
|
||||||
|
|
||||||
|
- it: should enable hostPID when specified
|
||||||
|
set:
|
||||||
|
podPrivileges.hostPID: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostPID
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# RUNTIME AND PRIORITY CLASSES
|
||||||
|
- it: should not set runtimeClassName by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.runtimeClassName
|
||||||
|
|
||||||
|
- it: should set runtimeClassName when specified
|
||||||
|
set:
|
||||||
|
runtimeClassName: nvidia
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.runtimeClassName
|
||||||
|
value: nvidia
|
||||||
|
|
||||||
|
- it: should not set priorityClassName by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.priorityClassName
|
||||||
|
|
||||||
|
- it: should set priorityClassName when specified
|
||||||
|
set:
|
||||||
|
priorityClassName: high-priority
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.priorityClassName
|
||||||
|
value: high-priority
|
||||||
|
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
- it: should configure for maximum security
|
||||||
|
set:
|
||||||
|
podSecurityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
runAsNonRoot: true
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.securityContext.runAsNonRoot
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation
|
||||||
|
value: false
|
||||||
|
|
||||||
|
- it: should configure for DLNA with hostNetwork
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
suite: test envFrom
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
- it: should not have envFrom by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
|
||||||
|
- it: should add envFrom from ConfigMap
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: jellyfin-config
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
|
||||||
|
value: jellyfin-config
|
||||||
|
|
||||||
|
- it: should add envFrom from Secret
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: jellyfin-secrets
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].secretRef.name
|
||||||
|
value: jellyfin-secrets
|
||||||
|
|
||||||
|
- it: should support multiple envFrom sources
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: jellyfin-config
|
||||||
|
- secretRef:
|
||||||
|
name: jellyfin-secrets
|
||||||
|
- configMapRef:
|
||||||
|
name: jellyfin-extra-config
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 3
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
|
||||||
|
value: jellyfin-config
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].secretRef.name
|
||||||
|
value: jellyfin-secrets
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[2].configMapRef.name
|
||||||
|
value: jellyfin-extra-config
|
||||||
|
|
||||||
|
- it: should support optional ConfigMap
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: optional-config
|
||||||
|
optional: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should support optional Secret
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: optional-secret
|
||||||
|
optional: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].secretRef.optional
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should support prefix for ConfigMap
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: my-config
|
||||||
|
prefix: CONFIG_
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
value: CONFIG_
|
||||||
|
|
||||||
|
- it: should support prefix for Secret
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: my-secret
|
||||||
|
prefix: SECRET_
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
value: SECRET_
|
||||||
|
|
||||||
|
- it: should work alongside regular env variables
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: CUSTOM_VAR
|
||||||
|
value: custom-value
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: jellyfin-config
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
content:
|
||||||
|
name: CUSTOM_VAR
|
||||||
|
value: custom-value
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
|
||||||
|
value: jellyfin-config
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND ADDITIONAL VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty envFrom array
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
|
||||||
|
- it: should support both configMapRef and secretRef with optional and prefix
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: my-config
|
||||||
|
optional: true
|
||||||
|
prefix: CFG_
|
||||||
|
- secretRef:
|
||||||
|
name: my-secret
|
||||||
|
optional: false
|
||||||
|
prefix: SEC_
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.name
|
||||||
|
value: my-config
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
value: CFG_
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].secretRef.name
|
||||||
|
value: my-secret
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].secretRef.optional
|
||||||
|
value: false
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].prefix
|
||||||
|
value: SEC_
|
||||||
|
|
||||||
|
- it: should support multiple ConfigMaps with different prefixes
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: config-1
|
||||||
|
prefix: APP1_
|
||||||
|
- configMapRef:
|
||||||
|
name: config-2
|
||||||
|
prefix: APP2_
|
||||||
|
- configMapRef:
|
||||||
|
name: config-3
|
||||||
|
prefix: APP3_
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 3
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
value: APP1_
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].prefix
|
||||||
|
value: APP2_
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[2].prefix
|
||||||
|
value: APP3_
|
||||||
|
|
||||||
|
- it: should support multiple Secrets with different prefixes
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: secret-1
|
||||||
|
prefix: DB_
|
||||||
|
- secretRef:
|
||||||
|
name: secret-2
|
||||||
|
prefix: API_
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
value: DB_
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[1].prefix
|
||||||
|
value: API_
|
||||||
|
|
||||||
|
- it: should not have prefix when not specified
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: no-prefix-config
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].prefix
|
||||||
|
|
||||||
|
- it: should not have optional field when not specified
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: required-config
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional
|
||||||
|
|
||||||
|
# Regression: verify envFrom doesn't interfere with other env settings
|
||||||
|
- it: should preserve JELLYFIN_PublishedServerUrl when envFrom is used
|
||||||
|
set:
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: https://jellyfin.example.com
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: extra-config
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].env
|
||||||
|
content:
|
||||||
|
name: JELLYFIN_PublishedServerUrl
|
||||||
|
value: https://jellyfin.example.com
|
||||||
|
|
||||||
|
# Complex scenario with many sources
|
||||||
|
- it: should support large number of envFrom sources
|
||||||
|
set:
|
||||||
|
jellyfin.envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: config-1
|
||||||
|
- secretRef:
|
||||||
|
name: secret-1
|
||||||
|
- configMapRef:
|
||||||
|
name: config-2
|
||||||
|
prefix: PREFIX_
|
||||||
|
- secretRef:
|
||||||
|
name: secret-2
|
||||||
|
optional: true
|
||||||
|
- configMapRef:
|
||||||
|
name: config-3
|
||||||
|
optional: true
|
||||||
|
prefix: OPT_
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].envFrom
|
||||||
|
count: 5
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
suite: test httproute
|
||||||
|
templates:
|
||||||
|
- httproute.yaml
|
||||||
|
tests:
|
||||||
|
- it: should not create HTTPRoute by default
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create HTTPRoute when enabled
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
namespace: gateway-system
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: HTTPRoute
|
||||||
|
- isAPIVersion:
|
||||||
|
of: gateway.networking.k8s.io/v1
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should add annotations when specified
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.annotations:
|
||||||
|
my-annotation: my-value
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.my-annotation
|
||||||
|
value: my-value
|
||||||
|
|
||||||
|
- it: should set parentRefs correctly
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
namespace: gateway-system
|
||||||
|
sectionName: https
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.parentRefs[0].name
|
||||||
|
value: my-gateway
|
||||||
|
- equal:
|
||||||
|
path: spec.parentRefs[0].namespace
|
||||||
|
value: gateway-system
|
||||||
|
- equal:
|
||||||
|
path: spec.parentRefs[0].sectionName
|
||||||
|
value: https
|
||||||
|
|
||||||
|
- it: should set hostnames when specified
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.hostnames:
|
||||||
|
- jellyfin.example.com
|
||||||
|
- media.example.com
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.hostnames[0]
|
||||||
|
value: jellyfin.example.com
|
||||||
|
- equal:
|
||||||
|
path: spec.hostnames[1]
|
||||||
|
value: media.example.com
|
||||||
|
|
||||||
|
- it: should create rules with path matches
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /jellyfin
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].matches[0].path.type
|
||||||
|
value: PathPrefix
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].matches[0].path.value
|
||||||
|
value: /jellyfin
|
||||||
|
|
||||||
|
- it: should set backendRefs to jellyfin service
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
service.port: 8096
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].backendRefs[0].name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].backendRefs[0].port
|
||||||
|
value: 8096
|
||||||
|
|
||||||
|
- it: should support multiple rules
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: Exact
|
||||||
|
value: /api
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].matches[0].path.value
|
||||||
|
value: /
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[1].matches[0].path.value
|
||||||
|
value: /api
|
||||||
|
|
||||||
|
- it: should support multiple matches in single rule
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /api
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /web
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[0].matches
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].matches[0].path.value
|
||||||
|
value: /api
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].matches[1].path.value
|
||||||
|
value: /web
|
||||||
|
|
||||||
|
- it: should use custom service port
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
service.port: 9000
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
httpRoute.rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].backendRefs[0].port
|
||||||
|
value: 9000
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
suite: test ingress
|
||||||
|
templates:
|
||||||
|
- ingress.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# DEFAULT BEHAVIOR
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create Ingress by default
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create Ingress when enabled
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: Ingress
|
||||||
|
- isAPIVersion:
|
||||||
|
of: networking.k8s.io/v1
|
||||||
|
|
||||||
|
- it: should have correct default name
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INGRESS CLASS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not set ingressClassName by default
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
|
||||||
|
- it: should set ingressClassName when specified
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: nginx
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
value: nginx
|
||||||
|
|
||||||
|
- it: should support various ingress classes
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: traefik
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
value: traefik
|
||||||
|
|
||||||
|
- it: should handle empty ingressClassName
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: ""
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ANNOTATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have annotations by default
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should add custom annotations
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.annotations:
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 100m
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["kubernetes.io/ingress.class"]
|
||||||
|
value: nginx
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["nginx.ingress.kubernetes.io/proxy-body-size"]
|
||||||
|
value: 100m
|
||||||
|
|
||||||
|
- it: should support multiple annotations
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["cert-manager.io/cluster-issuer"]
|
||||||
|
value: letsencrypt-prod
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["nginx.ingress.kubernetes.io/ssl-redirect"]
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["nginx.ingress.kubernetes.io/force-ssl-redirect"]
|
||||||
|
value: "true"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# HOSTS CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default host configuration
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].host
|
||||||
|
value: chart-example.local
|
||||||
|
|
||||||
|
- it: should set custom single host
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].host
|
||||||
|
value: jellyfin.example.com
|
||||||
|
|
||||||
|
- it: should support multiple hosts
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
- host: media.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].host
|
||||||
|
value: jellyfin.example.com
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[1].host
|
||||||
|
value: media.example.com
|
||||||
|
|
||||||
|
- it: should handle multiple paths for single host
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: example.com
|
||||||
|
paths:
|
||||||
|
- path: /jellyfin
|
||||||
|
pathType: Prefix
|
||||||
|
- path: /media
|
||||||
|
pathType: Exact
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[0].http.paths
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].path
|
||||||
|
value: /jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[1].path
|
||||||
|
value: /media
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PATH TYPES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should support Prefix pathType
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].pathType
|
||||||
|
value: Prefix
|
||||||
|
|
||||||
|
- it: should support Exact pathType
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /jellyfin
|
||||||
|
pathType: Exact
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].pathType
|
||||||
|
value: Exact
|
||||||
|
|
||||||
|
- it: should support ImplementationSpecific pathType
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].pathType
|
||||||
|
value: ImplementationSpecific
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# BACKEND SERVICE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should route to correct service and port
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].backend.service.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].backend.service.port.number
|
||||||
|
value: 8096
|
||||||
|
|
||||||
|
- it: should use custom service port when configured
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].backend.service.port.number
|
||||||
|
value: 9096
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TLS CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have TLS by default
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.tls
|
||||||
|
|
||||||
|
- it: should add TLS configuration when specified
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: jellyfin-tls
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.tls
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.tls
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.tls[0].secretName
|
||||||
|
value: jellyfin-tls
|
||||||
|
- contains:
|
||||||
|
path: spec.tls[0].hosts
|
||||||
|
content: jellyfin.example.com
|
||||||
|
|
||||||
|
- it: should support multiple TLS configurations
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: jellyfin-tls
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
- secretName: media-tls
|
||||||
|
hosts:
|
||||||
|
- media.example.com
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.tls
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.tls[0].secretName
|
||||||
|
value: jellyfin-tls
|
||||||
|
- equal:
|
||||||
|
path: spec.tls[1].secretName
|
||||||
|
value: media-tls
|
||||||
|
|
||||||
|
- it: should support TLS with multiple hosts in single certificate
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: wildcard-tls
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
- media.example.com
|
||||||
|
- streaming.example.com
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.tls[0].hosts
|
||||||
|
count: 3
|
||||||
|
- contains:
|
||||||
|
path: spec.tls[0].hosts
|
||||||
|
content: jellyfin.example.com
|
||||||
|
- contains:
|
||||||
|
path: spec.tls[0].hosts
|
||||||
|
content: media.example.com
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Empty hosts array (edge case - likely misconfiguration)
|
||||||
|
- it: should handle empty hosts array
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts: []
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# Empty paths array (edge case)
|
||||||
|
- it: should handle empty paths array
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths: []
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[0].http.paths
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# Multiple paths with different pathTypes
|
||||||
|
- it: should support mixed pathTypes in same host
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
- path: /api
|
||||||
|
pathType: Exact
|
||||||
|
- path: /media
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[0].http.paths
|
||||||
|
count: 3
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].pathType
|
||||||
|
value: Prefix
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[1].pathType
|
||||||
|
value: Exact
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[2].pathType
|
||||||
|
value: ImplementationSpecific
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create complete ingress with all features
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: nginx
|
||||||
|
ingress.annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 1g
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: jellyfin-tls-cert
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
value: nginx
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["cert-manager.io/cluster-issuer"]
|
||||||
|
value: letsencrypt-prod
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].host
|
||||||
|
value: jellyfin.example.com
|
||||||
|
- equal:
|
||||||
|
path: spec.tls[0].secretName
|
||||||
|
value: jellyfin-tls-cert
|
||||||
|
|
||||||
|
- it: should create multi-host multi-path ingress
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: traefik
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
- path: /admin
|
||||||
|
pathType: Exact
|
||||||
|
- host: media.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: example-com-tls
|
||||||
|
hosts:
|
||||||
|
- jellyfin.example.com
|
||||||
|
- media.example.com
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 2
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[0].http.paths
|
||||||
|
count: 2
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules[1].http.paths
|
||||||
|
count: 1
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.tls[0].hosts
|
||||||
|
count: 2
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always use networking.k8s.io/v1 API version
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: networking.k8s.io/v1
|
||||||
|
|
||||||
|
- it: should use service port number
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
service.port: 9999
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.rules[0].http.paths[0].backend.service.port.number
|
||||||
|
value: 9999
|
||||||
|
|
||||||
|
- it: should have correct label selectors
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
suite: test init containers
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
- it: should not have init containers by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
|
||||||
|
- it: should add extraInitContainers when specified
|
||||||
|
set:
|
||||||
|
extraInitContainers:
|
||||||
|
- name: init-config
|
||||||
|
image: busybox:1.35
|
||||||
|
command: ['sh', '-c', 'echo Initializing']
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: init-config
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].image
|
||||||
|
value: busybox:1.35
|
||||||
|
|
||||||
|
- it: should support multiple extraInitContainers
|
||||||
|
set:
|
||||||
|
extraInitContainers:
|
||||||
|
- name: init-1
|
||||||
|
image: busybox:1.35
|
||||||
|
- name: init-2
|
||||||
|
image: alpine:3.18
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: init-1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[1].name
|
||||||
|
value: init-2
|
||||||
|
|
||||||
|
- it: should preserve full container spec in extraInitContainers
|
||||||
|
set:
|
||||||
|
extraInitContainers:
|
||||||
|
- name: init-permissions
|
||||||
|
image: busybox:1.35
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args: ['chown -R 1000:1000 /config']
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].command[0]
|
||||||
|
value: sh
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].args[0]
|
||||||
|
value: 'chown -R 1000:1000 /config'
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].volumeMounts[0].name
|
||||||
|
value: config
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].securityContext.runAsUser
|
||||||
|
value: 0
|
||||||
|
|
||||||
|
# Deprecated initContainers (backward compatibility)
|
||||||
|
- it: should still support deprecated initContainers parameter
|
||||||
|
set:
|
||||||
|
initContainers:
|
||||||
|
- name: legacy-init
|
||||||
|
image: busybox:1.35
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: legacy-init
|
||||||
|
|
||||||
|
- it: should merge initContainers and extraInitContainers
|
||||||
|
set:
|
||||||
|
initContainers:
|
||||||
|
- name: legacy-init
|
||||||
|
image: busybox:1.35
|
||||||
|
extraInitContainers:
|
||||||
|
- name: new-init
|
||||||
|
image: alpine:3.18
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: legacy-init
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[1].name
|
||||||
|
value: new-init
|
||||||
|
|
||||||
|
- it: should prioritize extraInitContainers over initContainers
|
||||||
|
set:
|
||||||
|
initContainers:
|
||||||
|
- name: old-way
|
||||||
|
image: busybox:1.35
|
||||||
|
extraInitContainers:
|
||||||
|
- name: new-way
|
||||||
|
image: alpine:3.18
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
content:
|
||||||
|
name: new-way
|
||||||
|
image: alpine:3.18
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND FULL SPECIFICATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty extraInitContainers array
|
||||||
|
set:
|
||||||
|
extraInitContainers: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
|
||||||
|
- it: should preserve full container specification with all fields
|
||||||
|
set:
|
||||||
|
extraInitContainers:
|
||||||
|
- name: full-spec-init
|
||||||
|
image: busybox:1.35
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args: ['echo complete']
|
||||||
|
env:
|
||||||
|
- name: ENV_VAR
|
||||||
|
value: test
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: init-config
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
- name: media
|
||||||
|
mountPath: /media
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].imagePullPolicy
|
||||||
|
value: IfNotPresent
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].env[0].name
|
||||||
|
value: ENV_VAR
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].envFrom[0].configMapRef.name
|
||||||
|
value: init-config
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers[0].volumeMounts
|
||||||
|
count: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].resources.requests.cpu
|
||||||
|
value: 100m
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].resources.limits.memory
|
||||||
|
value: 256Mi
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].securityContext.allowPrivilegeEscalation
|
||||||
|
value: false
|
||||||
|
|
||||||
|
- it: should verify merge order (deprecated first, then extra)
|
||||||
|
set:
|
||||||
|
initContainers:
|
||||||
|
- name: first-deprecated
|
||||||
|
image: busybox:1.35
|
||||||
|
extraInitContainers:
|
||||||
|
- name: second-extra
|
||||||
|
image: alpine:3.18
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: first-deprecated
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[1].name
|
||||||
|
value: second-extra
|
||||||
|
|
||||||
|
- it: should support three or more init containers
|
||||||
|
set:
|
||||||
|
extraInitContainers:
|
||||||
|
- name: init-1
|
||||||
|
image: busybox:1.35
|
||||||
|
- name: init-2
|
||||||
|
image: alpine:3.18
|
||||||
|
- name: init-3
|
||||||
|
image: ubuntu:22.04
|
||||||
|
- name: init-4
|
||||||
|
image: debian:12
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 4
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[2].name
|
||||||
|
value: init-3
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[3].name
|
||||||
|
value: init-4
|
||||||
|
|
||||||
|
# Regression: verify initContainers comes first when merged
|
||||||
|
- it: should always place deprecated initContainers before extraInitContainers
|
||||||
|
set:
|
||||||
|
initContainers:
|
||||||
|
- name: deprecated-init
|
||||||
|
image: busybox:1.35
|
||||||
|
- name: deprecated-init-2
|
||||||
|
image: busybox:1.36
|
||||||
|
extraInitContainers:
|
||||||
|
- name: extra-init
|
||||||
|
image: alpine:3.18
|
||||||
|
- name: extra-init-2
|
||||||
|
image: alpine:3.19
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
count: 4
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[0].name
|
||||||
|
value: deprecated-init
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[1].name
|
||||||
|
value: deprecated-init-2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[2].name
|
||||||
|
value: extra-init
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.initContainers[3].name
|
||||||
|
value: extra-init-2
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
suite: test integration common configurations
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- httproute.yaml
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- serviceMonitor.yaml
|
||||||
|
- serviceaccount.yaml
|
||||||
|
- persistentVolumeClaim.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 1: DLNA HOME MEDIA SERVER
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure DLNA setup correctly
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/nas/media
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 30096
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
- template: deployment.yaml
|
||||||
|
contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/nas/media
|
||||||
|
type: Directory
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.type
|
||||||
|
value: NodePort
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 0 # NetworkPolicy incompatible with hostNetwork
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 2: SECURE PRODUCTION SETUP
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure secure production setup
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowMetadata: true
|
||||||
|
allowInCluster: true
|
||||||
|
allowedCIDRs: [10.0.0.0/8]
|
||||||
|
podSecurityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
persistence.config.size: 10Gi
|
||||||
|
persistence.media.size: 500Gi
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.size: 50Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 4Gi
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: nginx
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
ingress.tls:
|
||||||
|
- secretName: jellyfin-tls
|
||||||
|
hosts: [jellyfin.example.com]
|
||||||
|
asserts:
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.securityContext.runAsNonRoot
|
||||||
|
value: true
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation
|
||||||
|
value: false
|
||||||
|
- template: persistentVolumeClaim.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 3
|
||||||
|
- template: ingress.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 3: KUBERNETES WITH PROMETHEUS MONITORING
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure full metrics and monitoring
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.interval: 30s
|
||||||
|
metrics.serviceMonitor.labels:
|
||||||
|
prometheus: kube-prometheus
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.ingress.customRules:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: monitoring
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
podAnnotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8096"
|
||||||
|
asserts:
|
||||||
|
- template: serviceMonitor.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: serviceMonitor.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 30s
|
||||||
|
- template: deployment.yaml
|
||||||
|
isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle.postStart
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 4: IPv6-ONLY CLUSTER
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure IPv6-only setup
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: SingleStack
|
||||||
|
service.ipFamilies: [IPv6]
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.size: 1Ti
|
||||||
|
asserts:
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: SingleStack
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv6
|
||||||
|
- template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 1Ti
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 5: DUAL-STACK IPv4+IPv6
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure dual-stack networking
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: RequireDualStack
|
||||||
|
service.ipFamilies: [IPv4, IPv6]
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerIP: 203.0.113.42
|
||||||
|
asserts:
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: RequireDualStack
|
||||||
|
- template: service.yaml
|
||||||
|
lengthEqual:
|
||||||
|
path: spec.ipFamilies
|
||||||
|
count: 2
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.type
|
||||||
|
value: LoadBalancer
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 6: DEVELOPMENT/TESTING ENVIRONMENT
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure development environment
|
||||||
|
set:
|
||||||
|
replicaCount: 1
|
||||||
|
persistence.config.enabled: false
|
||||||
|
persistence.media.enabled: false
|
||||||
|
persistence.cache.enabled: false
|
||||||
|
image.tag: latest
|
||||||
|
image.pullPolicy: Always
|
||||||
|
revisionHistoryLimit: 1
|
||||||
|
deploymentStrategy.type: Recreate
|
||||||
|
jellyfin.env:
|
||||||
|
- name: JELLYFIN_LOG_LEVEL
|
||||||
|
value: Debug
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
- template: deployment.yaml
|
||||||
|
contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
emptyDir: {}
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: Always
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: Recreate
|
||||||
|
- template: persistentVolumeClaim.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 7: HIGH AVAILABILITY (with caveats)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Note: Jellyfin doesn't truly support HA, but users might try it
|
||||||
|
- it: should configure for attempted HA setup
|
||||||
|
set:
|
||||||
|
replicaCount: 3
|
||||||
|
persistence.config.accessMode: ReadWriteMany
|
||||||
|
persistence.media.accessMode: ReadWriteMany
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /nfs/jellyfin/media
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jellyfin
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 3
|
||||||
|
- template: persistentVolumeClaim.yaml
|
||||||
|
contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
documentIndex: 0
|
||||||
|
content: ReadWriteMany
|
||||||
|
- template: deployment.yaml
|
||||||
|
isNotNull:
|
||||||
|
path: spec.template.spec.affinity.podAntiAffinity
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 8: CLOUD PROVIDER SPECIFIC (AWS)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure for AWS with IRSA
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
serviceAccount.annotations:
|
||||||
|
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/jellyfin-s3
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.storageClass: gp3
|
||||||
|
persistence.media.size: 500Gi
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.annotations:
|
||||||
|
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||||
|
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.className: alb
|
||||||
|
ingress.annotations:
|
||||||
|
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||||
|
alb.ingress.kubernetes.io/target-type: ip
|
||||||
|
asserts:
|
||||||
|
- template: serviceaccount.yaml
|
||||||
|
equal:
|
||||||
|
path: metadata.annotations["eks.amazonaws.com/role-arn"]
|
||||||
|
value: arn:aws:iam::123456789012:role/jellyfin-s3
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: metadata.annotations["service.beta.kubernetes.io/aws-load-balancer-type"]
|
||||||
|
value: nlb
|
||||||
|
- template: ingress.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.ingressClassName
|
||||||
|
value: alb
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 9: GCP WITH WORKLOAD IDENTITY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure for GCP with Workload Identity
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
serviceAccount.annotations:
|
||||||
|
iam.gke.io/gcp-service-account: jellyfin@project-id.iam.gserviceaccount.com
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.storageClass: standard-rwo
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.annotations:
|
||||||
|
cloud.google.com/neg: '{"ingress": true}'
|
||||||
|
asserts:
|
||||||
|
- template: serviceaccount.yaml
|
||||||
|
equal:
|
||||||
|
path: metadata.annotations["iam.gke.io/gcp-service-account"]
|
||||||
|
value: jellyfin@project-id.iam.gserviceaccount.com
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: metadata.annotations["cloud.google.com/neg"]
|
||||||
|
value: '{"ingress": true}'
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 10: AIR-GAPPED ENVIRONMENT
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure for air-gapped environment
|
||||||
|
set:
|
||||||
|
image.repository: internal-registry.local/jellyfin/jellyfin
|
||||||
|
image.tag: 10.8.13-offline
|
||||||
|
image.pullPolicy: IfNotPresent
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: internal-registry-creds
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/local-storage/media
|
||||||
|
service.type: NodePort
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowInCluster: true
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
value: internal-registry.local/jellyfin/jellyfin:10.8.13-offline
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.imagePullSecrets[0].name
|
||||||
|
value: internal-registry-creds
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 11: MINIMAL RESOURCE-CONSTRAINED SETUP
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure minimal resource-constrained setup
|
||||||
|
set:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
persistence.config.size: 1Gi
|
||||||
|
persistence.media.type: emptyDir
|
||||||
|
persistence.cache.enabled: false
|
||||||
|
service.type: ClusterIP
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.containers[0].resources.requests.cpu
|
||||||
|
value: 100m
|
||||||
|
- template: persistentVolumeClaim.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1 # only config
|
||||||
|
- template: service.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.type
|
||||||
|
value: ClusterIP
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCENARIO 12: GATEWAY API (HTTPRoute)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should configure Gateway API HTTPRoute
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs:
|
||||||
|
- name: my-gateway
|
||||||
|
namespace: gateway-system
|
||||||
|
sectionName: https
|
||||||
|
httpRoute.hostnames:
|
||||||
|
- jellyfin.example.com
|
||||||
|
- media.example.com
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- template: httproute.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: httproute.yaml
|
||||||
|
lengthEqual:
|
||||||
|
path: spec.parentRefs
|
||||||
|
count: 1
|
||||||
|
- template: httproute.yaml
|
||||||
|
lengthEqual:
|
||||||
|
path: spec.hostnames
|
||||||
|
count: 2
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
suite: test networkpolicy
|
||||||
|
templates:
|
||||||
|
- networkpolicy.yaml
|
||||||
|
tests:
|
||||||
|
- it: should not create NetworkPolicy by default
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create NetworkPolicy when enabled
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: NetworkPolicy
|
||||||
|
- isAPIVersion:
|
||||||
|
of: networking.k8s.io/v1
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should fail when NetworkPolicy enabled with DLNA
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should fail when NetworkPolicy enabled with hostNetwork
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should have both Ingress and Egress policy types by default
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.policyTypes
|
||||||
|
content: Ingress
|
||||||
|
- contains:
|
||||||
|
path: spec.policyTypes
|
||||||
|
content: Egress
|
||||||
|
|
||||||
|
- it: should allow external ingress by default
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.ingress
|
||||||
|
# When allowExternal is true, there should be no 'from' in first ingress rule
|
||||||
|
- isNull:
|
||||||
|
path: spec.ingress[0].from
|
||||||
|
|
||||||
|
- it: should restrict ingress when allowExternal is false
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.ingress.allowExternal: false
|
||||||
|
networkPolicy.ingress.podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: test
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.ingress[0].from
|
||||||
|
- equal:
|
||||||
|
path: spec.ingress[0].from[0].podSelector.matchLabels.app
|
||||||
|
value: test
|
||||||
|
|
||||||
|
- it: should add namespace selector for ingress when specified
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.ingress.allowExternal: false
|
||||||
|
networkPolicy.ingress.namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: frontend
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ingress[0].from[0].namespaceSelector.matchLabels.name
|
||||||
|
value: frontend
|
||||||
|
|
||||||
|
- it: should create Prometheus ingress rule when metrics enabled
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.port: 8096
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.ingress
|
||||||
|
content:
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
from:
|
||||||
|
- podSelector:
|
||||||
|
app.kubernetes.io/name: prometheus
|
||||||
|
|
||||||
|
- it: should add Prometheus namespace selector when specified
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
networkPolicy.metrics.namespace: monitoring
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.ingress[1].from[0].namespaceSelector
|
||||||
|
- equal:
|
||||||
|
path: spec.ingress[1].from[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"]
|
||||||
|
value: monitoring
|
||||||
|
|
||||||
|
- it: should add custom ingress rules
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.ingress.customRules:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: custom
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 9999
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.ingress
|
||||||
|
content:
|
||||||
|
from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: custom
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 9999
|
||||||
|
|
||||||
|
- it: should allow DNS egress by default
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
|
||||||
|
- it: should allow custom DNS namespace
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.dnsNamespace: custom-dns-ns
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.egress[0].to[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"]
|
||||||
|
value: custom-dns-ns
|
||||||
|
|
||||||
|
- it: should allow custom DNS pod selector
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.dnsPodSelector:
|
||||||
|
k8s-app: coredns
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.egress[0].to[0].podSelector.matchLabels.k8s-app
|
||||||
|
value: coredns
|
||||||
|
|
||||||
|
- it: should allow all egress by default
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
|
||||||
|
- it: should not allow all egress when allowAllEgress is false
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
asserts:
|
||||||
|
- notContains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
|
||||||
|
- it: should allow HTTPS when restrictedEgress.allowMetadata is true
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress.allowMetadata: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
|
||||||
|
- it: should allow in-cluster communication when restrictedEgress.allowInCluster is true
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress.allowInCluster: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- podSelector: {}
|
||||||
|
|
||||||
|
- it: should add custom CIDR blocks
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress.allowedCIDRs:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 192.168.0.0/16
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.0.0/8
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 192.168.0.0/16
|
||||||
|
|
||||||
|
- it: should add custom egress rules
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.customRules:
|
||||||
|
- to:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: database
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5432
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: database
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5432
|
||||||
|
|
||||||
|
- it: should not create DNS egress when allowDNS is false
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowDNS: false
|
||||||
|
asserts:
|
||||||
|
- notContains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
|
||||||
|
- it: should use correct pod selector labels
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.podSelector.matchLabels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.podSelector.matchLabels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND ADDITIONAL VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# CIDR validations (IPv4 and IPv6)
|
||||||
|
- it: should accept valid IPv4 CIDR in allowedCIDRs
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowedCIDRs:
|
||||||
|
- "10.0.0.0/8"
|
||||||
|
- "192.168.1.0/24"
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.0.0/8
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 192.168.1.0/24
|
||||||
|
|
||||||
|
- it: should accept valid IPv6 CIDR in allowedCIDRs
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowedCIDRs:
|
||||||
|
- "2001:db8::/32"
|
||||||
|
- "fe80::/10"
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 2001:db8::/32
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: fe80::/10
|
||||||
|
|
||||||
|
- it: should accept mix of IPv4 and IPv6 CIDRs
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowedCIDRs:
|
||||||
|
- "10.0.0.0/8"
|
||||||
|
- "2001:db8::/32"
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.0.0/8
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 2001:db8::/32
|
||||||
|
|
||||||
|
# Egress restricted scenarios
|
||||||
|
- it: should allow only metadata when restrictedEgress with allowMetadata only
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowMetadata: true
|
||||||
|
allowInCluster: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
|
||||||
|
- it: should not allow in-cluster when allowInCluster is false
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowMetadata: false
|
||||||
|
allowInCluster: false
|
||||||
|
asserts:
|
||||||
|
- notContains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- podSelector: {}
|
||||||
|
|
||||||
|
- it: should handle empty allowedCIDRs array
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowedCIDRs: []
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.egress
|
||||||
|
|
||||||
|
# Custom ingress rules edge cases
|
||||||
|
- it: should support multiple custom ingress rules
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.ingress.customRules:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
name: frontend
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8096
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: proxy
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8920
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.ingress
|
||||||
|
count: 3 # default + 2 custom (when metrics disabled)
|
||||||
|
|
||||||
|
# Regression: verify default egress behavior
|
||||||
|
- it: should have DNS egress by default
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
|
||||||
|
# Regression: verify ingress port
|
||||||
|
- it: should expose correct port in ingress rules
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.ingress[0].ports
|
||||||
|
content:
|
||||||
|
protocol: TCP
|
||||||
|
port: http
|
||||||
|
|
||||||
|
# Combining all egress options
|
||||||
|
- it: should combine all egress options when all enabled
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
networkPolicy.egress.allowDNS: true
|
||||||
|
networkPolicy.egress.allowAllEgress: false
|
||||||
|
networkPolicy.egress.restrictedEgress:
|
||||||
|
allowMetadata: true
|
||||||
|
allowInCluster: true
|
||||||
|
allowedCIDRs:
|
||||||
|
- "10.0.0.0/8"
|
||||||
|
asserts:
|
||||||
|
# Should have DNS
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
# Should have metadata (HTTPS for movie/TV metadata providers)
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
# Should have in-cluster
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- podSelector: {}
|
||||||
|
# Should have custom CIDR
|
||||||
|
- contains:
|
||||||
|
path: spec.egress
|
||||||
|
content:
|
||||||
|
to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.0.0/8
|
||||||
@@ -0,0 +1,666 @@
|
|||||||
|
suite: test persistence (config, media, cache)
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
- persistentVolumeClaim.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# CONFIG PERSISTENCE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Default behavior
|
||||||
|
- it: should create config PVC by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2 # config and media (cache disabled by default)
|
||||||
|
|
||||||
|
- it: should set default config PVC size to 5Gi
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 5Gi
|
||||||
|
|
||||||
|
- it: should set default config PVC access mode to ReadWriteOnce
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteOnce
|
||||||
|
|
||||||
|
- it: should mount config PVC by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-config
|
||||||
|
|
||||||
|
- it: should mount config at /config
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
mountPath: /config
|
||||||
|
|
||||||
|
# Custom config PVC settings
|
||||||
|
- it: should set custom config PVC size
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.size: 10Gi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 10Gi
|
||||||
|
|
||||||
|
- it: should set custom config PVC access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.accessMode: ReadWriteMany
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteMany
|
||||||
|
|
||||||
|
- it: should set custom config PVC storage class
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.storageClass: fast-ssd
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.storageClassName
|
||||||
|
value: fast-ssd
|
||||||
|
|
||||||
|
- it: should add annotations to config PVC
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.annotations:
|
||||||
|
backup: "true"
|
||||||
|
retention: "30d"
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.backup
|
||||||
|
value: "true"
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.retention
|
||||||
|
value: "30d"
|
||||||
|
|
||||||
|
# Config with existing claim
|
||||||
|
- it: should use existing claim for config when specified
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.existingClaim: my-existing-config-claim
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: my-existing-config-claim
|
||||||
|
|
||||||
|
- it: should not create config PVC when existing claim used
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.existingClaim: my-existing-config-claim
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # media only
|
||||||
|
|
||||||
|
# Config disabled (emptyDir fallback)
|
||||||
|
- it: should use emptyDir for config when disabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.enabled: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should not create config PVC when disabled
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.enabled: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # media only
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MEDIA PERSISTENCE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Default behavior
|
||||||
|
- it: should create media PVC by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-media
|
||||||
|
|
||||||
|
- it: should set default media PVC size to 25Gi
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 25Gi
|
||||||
|
|
||||||
|
- it: should set default media PVC access mode to ReadWriteOnce
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteOnce
|
||||||
|
|
||||||
|
- it: should mount media PVC by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-media
|
||||||
|
|
||||||
|
- it: should mount media at /media
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
mountPath: /media
|
||||||
|
|
||||||
|
# Media with type: pvc (explicit)
|
||||||
|
- it: should create media PVC when type is pvc
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2 # config and media
|
||||||
|
|
||||||
|
- it: should set custom media PVC size
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.size: 100Gi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 100Gi
|
||||||
|
|
||||||
|
- it: should set custom media PVC storage class
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.storageClass: slow-hdd
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.storageClassName
|
||||||
|
value: slow-hdd
|
||||||
|
|
||||||
|
- it: should add annotations to media PVC
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.annotations:
|
||||||
|
media-type: movies
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.media-type
|
||||||
|
value: movies
|
||||||
|
|
||||||
|
# Media with type: hostPath
|
||||||
|
- it: should mount media from hostPath when configured
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/nas/media
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/nas/media
|
||||||
|
type: Directory
|
||||||
|
|
||||||
|
- it: should not create media PVC when type is hostPath
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/nas/media
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # config only
|
||||||
|
|
||||||
|
# Media with type: emptyDir
|
||||||
|
- it: should mount media as emptyDir when type is emptyDir
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should not create media PVC when type is emptyDir
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # config only
|
||||||
|
|
||||||
|
# Media disabled
|
||||||
|
- it: should use emptyDir for media when disabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.enabled: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should not create media PVC when disabled
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.enabled: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # config only
|
||||||
|
|
||||||
|
# Media with existing claim
|
||||||
|
- it: should use existing claim for media when specified
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.existingClaim: my-existing-media-claim
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: my-existing-media-claim
|
||||||
|
|
||||||
|
- it: should not create media PVC when existing claim used
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.existingClaim: my-existing-media-claim
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # config only
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CACHE PERSISTENCE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Default behavior
|
||||||
|
- it: should mount cache as emptyDir by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should not create cache PVC by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2 # config and media only
|
||||||
|
|
||||||
|
- it: should mount cache at /cache
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
mountPath: /cache
|
||||||
|
|
||||||
|
# Cache with type: pvc
|
||||||
|
- it: should create cache PVC when enabled with type pvc
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 3 # config, media, and cache
|
||||||
|
|
||||||
|
- it: should mount cache PVC when enabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-cache
|
||||||
|
|
||||||
|
- it: should set cache PVC size correctly
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 2
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.size: 20Gi
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 20Gi
|
||||||
|
|
||||||
|
- it: should set cache PVC access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 2
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.accessMode: ReadWriteMany
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteMany
|
||||||
|
|
||||||
|
- it: should set cache PVC storage class
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 2
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.storageClass: fast-ssd
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.storageClassName
|
||||||
|
value: fast-ssd
|
||||||
|
|
||||||
|
- it: should add annotations to cache PVC
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 2
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.annotations:
|
||||||
|
my-annotation: my-value
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.my-annotation
|
||||||
|
value: my-value
|
||||||
|
|
||||||
|
# Cache with type: hostPath
|
||||||
|
- it: should mount cache from hostPath when configured
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: hostPath
|
||||||
|
persistence.cache.hostPath: /mnt/jellyfin-cache
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/jellyfin-cache
|
||||||
|
type: Directory
|
||||||
|
|
||||||
|
- it: should not create cache PVC when type is hostPath
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: hostPath
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2 # config and media only
|
||||||
|
|
||||||
|
# Cache with type: emptyDir
|
||||||
|
- it: should not create cache PVC when type is emptyDir
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2
|
||||||
|
|
||||||
|
# Cache with existing claim
|
||||||
|
- it: should use existing claim for cache when specified
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.existingClaim: my-existing-cache-claim
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: my-existing-cache-claim
|
||||||
|
|
||||||
|
- it: should not create PVC when existing cache claim used
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
persistence.cache.existingClaim: my-existing-cache-claim
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2 # config and media only, not cache
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# All persistence disabled
|
||||||
|
- it: should use emptyDir for all volumes when all persistence disabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.enabled: false
|
||||||
|
persistence.media.enabled: false
|
||||||
|
persistence.cache.enabled: false
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
emptyDir: {}
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
emptyDir: {}
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should not create any PVCs when all persistence disabled
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.enabled: false
|
||||||
|
persistence.media.enabled: false
|
||||||
|
persistence.cache.enabled: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# All persistence enabled as PVC
|
||||||
|
- it: should create all three PVCs when all enabled
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: pvc
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 3 # config, media, and cache
|
||||||
|
|
||||||
|
# Mix of persistence types
|
||||||
|
- it: should handle mix of pvc, hostPath, and emptyDir
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.config.enabled: true
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/media
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/media
|
||||||
|
type: Directory
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should create only config PVC with mixed types
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/media
|
||||||
|
persistence.cache.enabled: true
|
||||||
|
persistence.cache.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # config only
|
||||||
|
|
||||||
|
# All access modes
|
||||||
|
- it: should support ReadWriteOnce access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.accessMode: ReadWriteOnce
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteOnce
|
||||||
|
|
||||||
|
- it: should support ReadOnlyMany access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
set:
|
||||||
|
persistence.media.accessMode: ReadOnlyMany
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadOnlyMany
|
||||||
|
|
||||||
|
- it: should support ReadWriteMany access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.accessMode: ReadWriteMany
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteMany
|
||||||
|
|
||||||
|
- it: should support ReadWriteOncePod access mode
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.accessMode: ReadWriteOncePod
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteOncePod
|
||||||
|
|
||||||
|
# Empty storageClass (default provisioner)
|
||||||
|
- it: should not set storageClassName when storageClass is empty string
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.storageClass: ''
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.storageClassName
|
||||||
|
|
||||||
|
# Multiple annotations
|
||||||
|
- it: should support multiple annotations on PVC
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
set:
|
||||||
|
persistence.config.annotations:
|
||||||
|
annotation1: value1
|
||||||
|
annotation2: value2
|
||||||
|
annotation3: value3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation1
|
||||||
|
value: value1
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation2
|
||||||
|
value: value2
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation3
|
||||||
|
value: value3
|
||||||
|
|
||||||
|
# Regression: verify all volume mounts are always present
|
||||||
|
- it: should always have all three volume mounts in container
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 3
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
mountPath: /config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
mountPath: /media
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
mountPath: /cache
|
||||||
|
|
||||||
|
# Regression: verify all volumes are always present
|
||||||
|
- it: should always have all three volumes defined
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 3
|
||||||
@@ -0,0 +1,447 @@
|
|||||||
|
suite: test probes (startup, liveness, readiness)
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# STARTUP PROBE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have startup probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe
|
||||||
|
|
||||||
|
- it: should use tcpSocket for startup probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should have default startup probe timing values
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
|
||||||
|
value: 0
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.periodSeconds
|
||||||
|
value: 10
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
||||||
|
value: 30
|
||||||
|
|
||||||
|
- it: should allow custom initialDelaySeconds for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe.initialDelaySeconds: 5
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
|
||||||
|
value: 5
|
||||||
|
|
||||||
|
- it: should allow custom periodSeconds for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe.periodSeconds: 15
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.periodSeconds
|
||||||
|
value: 15
|
||||||
|
|
||||||
|
- it: should allow custom failureThreshold for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe.failureThreshold: 60
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
||||||
|
value: 60
|
||||||
|
|
||||||
|
- it: should support httpGet for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.httpGet
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.httpGet.path
|
||||||
|
value: /health
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.httpGet.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should support exec for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe:
|
||||||
|
exec:
|
||||||
|
command: ['/bin/sh', '-c', 'curl -f http://localhost:8096/health']
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.exec
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.exec.command
|
||||||
|
content: '/bin/sh'
|
||||||
|
|
||||||
|
- it: should support custom port for startup probe
|
||||||
|
set:
|
||||||
|
startupProbe.tcpSocket.port: 9000
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket.port
|
||||||
|
value: 9000
|
||||||
|
|
||||||
|
- it: should calculate correct startup timeout window
|
||||||
|
set:
|
||||||
|
startupProbe.periodSeconds: 10
|
||||||
|
startupProbe.failureThreshold: 30
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.periodSeconds
|
||||||
|
value: 10
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
||||||
|
value: 30
|
||||||
|
# Total startup time allowed: 10 * 30 = 300 seconds (5 minutes)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LIVENESS PROBE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have liveness probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe
|
||||||
|
|
||||||
|
- it: should use httpGet for liveness probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.path
|
||||||
|
value: /health
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should have default liveness probe initialDelaySeconds
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds
|
||||||
|
value: 10
|
||||||
|
|
||||||
|
- it: should allow custom initialDelaySeconds for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.initialDelaySeconds: 30
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds
|
||||||
|
value: 30
|
||||||
|
|
||||||
|
- it: should allow custom periodSeconds for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.periodSeconds: 20
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.periodSeconds
|
||||||
|
value: 20
|
||||||
|
|
||||||
|
- it: should allow custom failureThreshold for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.failureThreshold: 5
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.failureThreshold
|
||||||
|
value: 5
|
||||||
|
|
||||||
|
- it: should allow custom timeoutSeconds for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.timeoutSeconds: 5
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds
|
||||||
|
value: 5
|
||||||
|
|
||||||
|
- it: should allow custom successThreshold for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.successThreshold: 2
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.successThreshold
|
||||||
|
value: 2
|
||||||
|
|
||||||
|
- it: should support tcpSocket for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.tcpSocket
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.tcpSocket.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should support exec for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ['/bin/sh', '-c', 'pgrep jellyfin']
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.exec
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.exec.command
|
||||||
|
content: 'pgrep jellyfin'
|
||||||
|
|
||||||
|
- it: should support custom httpGet path for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe.httpGet.path: /custom/health
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.path
|
||||||
|
value: /custom/health
|
||||||
|
|
||||||
|
- it: should support httpHeaders for liveness probe
|
||||||
|
set:
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Custom-Header
|
||||||
|
value: CustomValue
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.httpHeaders[0].name
|
||||||
|
value: X-Custom-Header
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.httpHeaders[0].value
|
||||||
|
value: CustomValue
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# READINESS PROBE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have readiness probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe
|
||||||
|
|
||||||
|
- it: should use httpGet for readiness probe by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.path
|
||||||
|
value: /health
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should have default readiness probe initialDelaySeconds
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds
|
||||||
|
value: 10
|
||||||
|
|
||||||
|
- it: should allow custom initialDelaySeconds for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.initialDelaySeconds: 15
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds
|
||||||
|
value: 15
|
||||||
|
|
||||||
|
- it: should allow custom periodSeconds for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.periodSeconds: 15
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.periodSeconds
|
||||||
|
value: 15
|
||||||
|
|
||||||
|
- it: should allow custom failureThreshold for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.failureThreshold: 6
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.failureThreshold
|
||||||
|
value: 6
|
||||||
|
|
||||||
|
- it: should allow custom timeoutSeconds for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.timeoutSeconds: 3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
- it: should allow custom successThreshold for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.successThreshold: 2
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.successThreshold
|
||||||
|
value: 2
|
||||||
|
|
||||||
|
- it: should support tcpSocket for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.tcpSocket
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.tcpSocket.port
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should support exec for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ['/bin/sh', '-c', 'curl -f http://localhost:8096/health']
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.exec
|
||||||
|
|
||||||
|
- it: should support custom httpGet path for readiness probe
|
||||||
|
set:
|
||||||
|
readinessProbe.httpGet.path: /ready
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.path
|
||||||
|
value: /ready
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND COMBINED SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should allow all three probes with different configurations
|
||||||
|
set:
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 60
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 20
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 10
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
||||||
|
value: 60
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds
|
||||||
|
value: 30
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds
|
||||||
|
value: 15
|
||||||
|
|
||||||
|
- it: should support all timing parameters for all probes
|
||||||
|
set:
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 15
|
||||||
|
failureThreshold: 4
|
||||||
|
timeoutSeconds: 3
|
||||||
|
successThreshold: 1
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
timeoutSeconds: 2
|
||||||
|
successThreshold: 2
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.timeoutSeconds
|
||||||
|
value: 5
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds
|
||||||
|
value: 3
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds
|
||||||
|
value: 2
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.successThreshold
|
||||||
|
value: 2
|
||||||
|
|
||||||
|
# Regression: verify all three probes are always present
|
||||||
|
- it: should always have all three probes configured
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe
|
||||||
|
|
||||||
|
# Regression: verify default probe types
|
||||||
|
- it: should use correct probe types by default
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet
|
||||||
|
|
||||||
|
# Different probe types combination
|
||||||
|
- it: should support different probe types for each probe
|
||||||
|
set:
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ['/bin/sh', '-c', 'test -f /tmp/ready']
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.exec
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
suite: test regression defaults
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- serviceaccount.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- httproute.yaml
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- serviceMonitor.yaml
|
||||||
|
- persistentVolumeClaim.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# DEPLOYMENT DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default replica count of 1
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should use RollingUpdate strategy by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.strategy.type
|
||||||
|
value: RollingUpdate
|
||||||
|
|
||||||
|
- it: should have revisionHistoryLimit of 3 by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
- it: should use default image jellyfin/jellyfin
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- matchRegex:
|
||||||
|
path: spec.template.spec.containers[0].image
|
||||||
|
pattern: "^docker\\.io/jellyfin/jellyfin:"
|
||||||
|
|
||||||
|
- it: should use IfNotPresent pullPolicy by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].imagePullPolicy
|
||||||
|
value: IfNotPresent
|
||||||
|
|
||||||
|
- it: should not have resources set by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].resources
|
||||||
|
|
||||||
|
- it: should not set dnsPolicy by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
|
||||||
|
- it: should not enable hostNetwork by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should not have podSecurityContext by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.securityContext
|
||||||
|
|
||||||
|
- it: should not have container securityContext by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].securityContext
|
||||||
|
|
||||||
|
- it: should expose port 8096 by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].containerPort
|
||||||
|
value: 8096
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].ports[0].name
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should have exactly 3 default volumes
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
- it: should have config, media, and cache volumes by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-media
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
- it: should mount all three volumes by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 3
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: config
|
||||||
|
mountPath: /config
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
mountPath: /media
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
mountPath: /cache
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PROBE DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have startup probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe
|
||||||
|
|
||||||
|
- it: should use tcpSocket for startup probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.tcpSocket
|
||||||
|
|
||||||
|
- it: should have default startup probe timing
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds
|
||||||
|
value: 0
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.periodSeconds
|
||||||
|
value: 10
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe.failureThreshold
|
||||||
|
value: 30
|
||||||
|
|
||||||
|
- it: should have liveness probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe
|
||||||
|
|
||||||
|
- it: should use httpGet for liveness probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.httpGet.path
|
||||||
|
value: /health
|
||||||
|
|
||||||
|
- it: should have default liveness probe initialDelay of 10
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds
|
||||||
|
value: 10
|
||||||
|
|
||||||
|
- it: should have readiness probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe
|
||||||
|
|
||||||
|
- it: should use httpGet for readiness probe by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe.httpGet.path
|
||||||
|
value: /health
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SERVICE DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create Service by default
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should use ClusterIP type by default
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: ClusterIP
|
||||||
|
|
||||||
|
- it: should expose port 8096 by default
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].port
|
||||||
|
value: 8096
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].name
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should not have ipFamilyPolicy set by default
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
|
||||||
|
- it: should not have ipFamilies set by default
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ipFamilies
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SERVICEACCOUNT DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create ServiceAccount by default
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should enable automount by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PERSISTENCE DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create config and media PVCs by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2
|
||||||
|
|
||||||
|
- it: should have config persistence enabled with size 5Gi by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-config
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 5Gi
|
||||||
|
|
||||||
|
- it: should have media persistence enabled with size 25Gi by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-media
|
||||||
|
- equal:
|
||||||
|
path: spec.resources.requests.storage
|
||||||
|
value: 25Gi
|
||||||
|
|
||||||
|
- it: should use ReadWriteOnce accessMode by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.accessModes
|
||||||
|
content: ReadWriteOnce
|
||||||
|
|
||||||
|
- it: should not create cache PVC by default
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 2
|
||||||
|
|
||||||
|
- it: should use emptyDir for cache by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: cache
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# OPTIONAL FEATURES DISABLED BY DEFAULT
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create Ingress by default
|
||||||
|
template: ingress.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create HTTPRoute by default
|
||||||
|
template: httproute.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create NetworkPolicy by default
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor by default
|
||||||
|
template: serviceMonitor.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not enable DLNA by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should not enable metrics by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.containers[0].lifecycle
|
||||||
|
|
||||||
|
- it: should not have init containers by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.initContainers
|
||||||
|
|
||||||
|
- it: should not have extra containers by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should not have imagePullSecrets by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.imagePullSecrets
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LABEL DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have standard Kubernetes labels on deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["app.kubernetes.io/version"]
|
||||||
|
- isNotNull:
|
||||||
|
path: metadata.labels["helm.sh/chart"]
|
||||||
|
|
||||||
|
- it: should have standard labels on service
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should have standard labels on serviceaccount
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should have standard labels on PVCs
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# API VERSIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use apps/v1 for Deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: apps/v1
|
||||||
|
|
||||||
|
- it: should use v1 for Service
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: v1
|
||||||
|
|
||||||
|
- it: should use v1 for ServiceAccount
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: v1
|
||||||
|
|
||||||
|
- it: should use v1 for PersistentVolumeClaim
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION: VERIFY NO UNEXPECTED CHANGES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should maintain exactly one container by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should maintain container name as jellyfin
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.containers[0].name
|
||||||
|
value: jellyfin
|
||||||
|
|
||||||
|
- it: should maintain service name pattern
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should maintain serviceAccount name pattern
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should maintain deployment name pattern
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should maintain PVC naming pattern (config)
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-config
|
||||||
|
|
||||||
|
- it: should maintain PVC naming pattern (media)
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
documentIndex: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin-media
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CRITICAL DEFAULTS THAT MUST NOT CHANGE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should never create multiple replicas by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should never enable hostNetwork by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should never enable NetworkPolicy by default
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should always create exactly one service
|
||||||
|
template: service.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should always create serviceAccount by default
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should always have three probes configured
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].startupProbe
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].livenessProbe
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.containers[0].readinessProbe
|
||||||
|
|
||||||
|
- it: should always mount all persistence volumes
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.template.spec.containers[0].volumeMounts
|
||||||
|
count: 3
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
suite: test revision history limit
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
- it: should set revisionHistoryLimit to 3 by default
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
- it: should allow custom revisionHistoryLimit
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 5
|
||||||
|
|
||||||
|
- it: should allow revisionHistoryLimit of 0
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 0
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 0
|
||||||
|
|
||||||
|
- it: should allow revisionHistoryLimit of 1
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should allow large revisionHistoryLimit
|
||||||
|
set:
|
||||||
|
revisionHistoryLimit: 100
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.revisionHistoryLimit
|
||||||
|
value: 100
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
suite: test service
|
||||||
|
templates:
|
||||||
|
- service.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# BASIC SERVICE DEFAULTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create Service by default
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: Service
|
||||||
|
- isAPIVersion:
|
||||||
|
of: v1
|
||||||
|
|
||||||
|
- it: should have correct default name
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should use ClusterIP type by default
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: ClusterIP
|
||||||
|
|
||||||
|
- it: should expose port 8096 by default
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].port
|
||||||
|
value: 8096
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].targetPort
|
||||||
|
value: http
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].protocol
|
||||||
|
value: TCP
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].name
|
||||||
|
value: http
|
||||||
|
|
||||||
|
- it: should have correct pod selector labels
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.selector["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.selector["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SERVICE TYPE TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should support ClusterIP type explicitly
|
||||||
|
set:
|
||||||
|
service.type: ClusterIP
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: ClusterIP
|
||||||
|
- isNull:
|
||||||
|
path: spec.clusterIP
|
||||||
|
|
||||||
|
- it: should support NodePort type
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: NodePort
|
||||||
|
|
||||||
|
- it: should support LoadBalancer type
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: LoadBalancer
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PORT CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should set custom port
|
||||||
|
set:
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].port
|
||||||
|
value: 9096
|
||||||
|
|
||||||
|
- it: should set custom nodePort when type is NodePort
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 30096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 30096
|
||||||
|
|
||||||
|
- it: should not set nodePort when type is ClusterIP
|
||||||
|
set:
|
||||||
|
service.type: ClusterIP
|
||||||
|
service.nodePort: 30096
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
|
||||||
|
# Port range validation would be done by values.schema.json
|
||||||
|
# Here we just test that values are applied correctly
|
||||||
|
- it: should accept nodePort in valid range (30000-32767)
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 30000
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 30000
|
||||||
|
|
||||||
|
- it: should accept nodePort at upper range
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 32767
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 32767
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LOADBALANCER CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should set loadBalancerIP when specified
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerIP: 203.0.113.42
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.loadBalancerIP
|
||||||
|
value: 203.0.113.42
|
||||||
|
|
||||||
|
- it: should set loadBalancerClass when specified
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerClass: metallb
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.loadBalancerClass
|
||||||
|
value: metallb
|
||||||
|
|
||||||
|
- it: should set loadBalancerSourceRanges when specified
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerSourceRanges:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 192.168.0.0/16
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.loadBalancerSourceRanges
|
||||||
|
content: 10.0.0.0/8
|
||||||
|
- contains:
|
||||||
|
path: spec.loadBalancerSourceRanges
|
||||||
|
content: 192.168.0.0/16
|
||||||
|
|
||||||
|
- it: should not set loadBalancerIP when type is ClusterIP
|
||||||
|
set:
|
||||||
|
service.type: ClusterIP
|
||||||
|
service.loadBalancerIP: 203.0.113.42
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.loadBalancerIP
|
||||||
|
|
||||||
|
- it: should set all loadBalancer options together
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerIP: 203.0.113.42
|
||||||
|
service.loadBalancerClass: metallb
|
||||||
|
service.loadBalancerSourceRanges:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.loadBalancerIP
|
||||||
|
value: 203.0.113.42
|
||||||
|
- equal:
|
||||||
|
path: spec.loadBalancerClass
|
||||||
|
value: metallb
|
||||||
|
- contains:
|
||||||
|
path: spec.loadBalancerSourceRanges
|
||||||
|
content: 10.0.0.0/8
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ANNOTATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have annotations by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should add custom annotations
|
||||||
|
set:
|
||||||
|
service.annotations:
|
||||||
|
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: jellyfin.example.com
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["service.beta.kubernetes.io/aws-load-balancer-type"]
|
||||||
|
value: nlb
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["external-dns.alpha.kubernetes.io/hostname"]
|
||||||
|
value: jellyfin.example.com
|
||||||
|
|
||||||
|
- it: should support empty annotations object
|
||||||
|
set:
|
||||||
|
service.annotations: {}
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# IPv4/IPv6 DUAL-STACK TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not set ipFamilyPolicy by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
|
||||||
|
- it: should not set ipFamilies by default
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.ipFamilies
|
||||||
|
|
||||||
|
- it: should set ipFamilyPolicy when specified
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: PreferDualStack
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: PreferDualStack
|
||||||
|
|
||||||
|
- it: should support SingleStack policy
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: SingleStack
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: SingleStack
|
||||||
|
|
||||||
|
- it: should support RequireDualStack policy
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: RequireDualStack
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: RequireDualStack
|
||||||
|
|
||||||
|
- it: should set ipFamilies for IPv4 only
|
||||||
|
set:
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv4
|
||||||
|
|
||||||
|
- it: should set ipFamilies for IPv6 only
|
||||||
|
set:
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv6
|
||||||
|
|
||||||
|
- it: should set ipFamilies for dual-stack IPv4 primary
|
||||||
|
set:
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
- IPv6
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv4
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[1]
|
||||||
|
value: IPv6
|
||||||
|
|
||||||
|
- it: should set ipFamilies for dual-stack IPv6 primary
|
||||||
|
set:
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
- IPv4
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv6
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[1]
|
||||||
|
value: IPv4
|
||||||
|
|
||||||
|
- it: should work with PreferDualStack and both families
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: PreferDualStack
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
- IPv6
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: PreferDualStack
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv4
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[1]
|
||||||
|
value: IPv6
|
||||||
|
|
||||||
|
- it: should work with RequireDualStack and both families
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: RequireDualStack
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
- IPv4
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: RequireDualStack
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv6
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[1]
|
||||||
|
value: IPv4
|
||||||
|
|
||||||
|
- it: should work with SingleStack IPv6 only
|
||||||
|
set:
|
||||||
|
service.ipFamilyPolicy: SingleStack
|
||||||
|
service.ipFamilies:
|
||||||
|
- IPv6
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilyPolicy
|
||||||
|
value: SingleStack
|
||||||
|
- equal:
|
||||||
|
path: spec.ipFamilies[0]
|
||||||
|
value: IPv6
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES AND VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# LoadBalancer without explicit IP (valid use case)
|
||||||
|
- it: should create LoadBalancer without loadBalancerIP
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: LoadBalancer
|
||||||
|
- isNull:
|
||||||
|
path: spec.loadBalancerIP
|
||||||
|
|
||||||
|
# NodePort without explicit nodePort (auto-assign)
|
||||||
|
- it: should create NodePort without explicit nodePort
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: NodePort
|
||||||
|
- isNull:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
|
||||||
|
# Multiple loadBalancerSourceRanges
|
||||||
|
- it: should support multiple loadBalancerSourceRanges
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
service.loadBalancerSourceRanges:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 172.16.0.0/12
|
||||||
|
- 192.168.0.0/16
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.loadBalancerSourceRanges
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
# Regression: verify service always has one port
|
||||||
|
- it: should always have exactly one port defined
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.ports
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# Regression: verify port name is always http
|
||||||
|
- it: should always name port as http
|
||||||
|
set:
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].name
|
||||||
|
value: http
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].targetPort
|
||||||
|
value: http
|
||||||
|
|
||||||
|
# Combining multiple settings
|
||||||
|
- it: should combine custom port with NodePort and annotations
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.port: 9096
|
||||||
|
service.nodePort: 31096
|
||||||
|
service.annotations:
|
||||||
|
custom: annotation
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: NodePort
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].port
|
||||||
|
value: 9096
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 31096
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.custom
|
||||||
|
value: annotation
|
||||||
|
|
||||||
|
# Regression: verify protocol is always TCP
|
||||||
|
- it: should always use TCP protocol
|
||||||
|
set:
|
||||||
|
service.port: 9096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].protocol
|
||||||
|
value: TCP
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
suite: test serviceaccount
|
||||||
|
templates:
|
||||||
|
- serviceaccount.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# DEFAULT BEHAVIOR
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create ServiceAccount by default
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: ServiceAccount
|
||||||
|
- isAPIVersion:
|
||||||
|
of: v1
|
||||||
|
|
||||||
|
- it: should have auto-generated name by default
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should use ServiceAccount in deployment by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should automount service account token by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CREATE CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create ServiceAccount when create is false
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should use default ServiceAccount when create is false
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: false
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
value: default
|
||||||
|
|
||||||
|
- it: should create ServiceAccount when explicitly enabled
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CUSTOM NAME
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should use custom name when specified
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.name: my-custom-sa
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: my-custom-sa
|
||||||
|
|
||||||
|
- it: should use custom name in deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.name: my-custom-sa
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
value: my-custom-sa
|
||||||
|
|
||||||
|
- it: should use custom name when create is false
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: false
|
||||||
|
serviceAccount.name: existing-sa
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
value: existing-sa
|
||||||
|
|
||||||
|
- it: should not create SA but use custom name when create is false
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: false
|
||||||
|
serviceAccount.name: existing-external-sa
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ANNOTATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have annotations by default
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should add custom annotations
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations:
|
||||||
|
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/jellyfin-role
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["eks.amazonaws.com/role-arn"]
|
||||||
|
value: arn:aws:iam::123456789012:role/jellyfin-role
|
||||||
|
|
||||||
|
- it: should support multiple annotations
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations:
|
||||||
|
annotation1: value1
|
||||||
|
annotation2: value2
|
||||||
|
annotation3: value3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation1
|
||||||
|
value: value1
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation2
|
||||||
|
value: value2
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.annotation3
|
||||||
|
value: value3
|
||||||
|
|
||||||
|
- it: should support GCP Workload Identity annotation
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations:
|
||||||
|
iam.gke.io/gcp-service-account: jellyfin@project-id.iam.gserviceaccount.com
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["iam.gke.io/gcp-service-account"]
|
||||||
|
value: jellyfin@project-id.iam.gserviceaccount.com
|
||||||
|
|
||||||
|
- it: should support Azure Workload Identity annotation
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations:
|
||||||
|
azure.workload.identity/client-id: 12345678-1234-1234-1234-123456789012
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["azure.workload.identity/client-id"]
|
||||||
|
value: 12345678-1234-1234-1234-123456789012
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# AUTOMOUNT CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should enable automount by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should disable automount when set to false
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.automount: false
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: false
|
||||||
|
|
||||||
|
- it: should explicitly enable automount when set to true
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.automount: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should handle empty name gracefully (use auto-generated)
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.name: ""
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should handle empty annotations object
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations: {}
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.annotations
|
||||||
|
|
||||||
|
- it: should work with custom name and annotations together
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.name: custom-sa
|
||||||
|
serviceAccount.annotations:
|
||||||
|
custom: annotation
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: custom-sa
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations.custom
|
||||||
|
value: annotation
|
||||||
|
|
||||||
|
- it: should work with all options combined
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
serviceAccount.name: jellyfin-prod-sa
|
||||||
|
serviceAccount.annotations:
|
||||||
|
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/jellyfin
|
||||||
|
custom.io/annotation: value
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: jellyfin-prod-sa
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["eks.amazonaws.com/role-arn"]
|
||||||
|
value: arn:aws:iam::123456789012:role/jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["custom.io/annotation"]
|
||||||
|
value: value
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always have correct labels
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should always use v1 API version
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: v1
|
||||||
|
|
||||||
|
- it: should always be ServiceAccount kind
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: kind
|
||||||
|
value: ServiceAccount
|
||||||
|
|
||||||
|
# Verify deployment always sets serviceAccountName
|
||||||
|
- it: should always set serviceAccountName in deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
|
||||||
|
- it: should always set automountServiceAccountToken in deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INTEGRATION SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Scenario: Using existing external ServiceAccount
|
||||||
|
- it: should use existing external ServiceAccount without creating new one
|
||||||
|
set:
|
||||||
|
serviceAccount.create: false
|
||||||
|
serviceAccount.name: external-jellyfin-sa
|
||||||
|
serviceAccount.automount: false
|
||||||
|
asserts:
|
||||||
|
- template: serviceaccount.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 0
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.serviceAccountName
|
||||||
|
value: external-jellyfin-sa
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: false
|
||||||
|
|
||||||
|
# Scenario: AWS IRSA (IAM Roles for Service Accounts)
|
||||||
|
- it: should configure for AWS IRSA
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
serviceAccount.name: jellyfin-irsa
|
||||||
|
serviceAccount.annotations:
|
||||||
|
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/jellyfin-s3-access
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: jellyfin-irsa
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["eks.amazonaws.com/role-arn"]
|
||||||
|
value: arn:aws:iam::123456789012:role/jellyfin-s3-access
|
||||||
|
|
||||||
|
# Scenario: GCP Workload Identity
|
||||||
|
- it: should configure for GCP Workload Identity
|
||||||
|
template: serviceaccount.yaml
|
||||||
|
set:
|
||||||
|
serviceAccount.annotations:
|
||||||
|
iam.gke.io/gcp-service-account: jellyfin@my-project.iam.gserviceaccount.com
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.annotations["iam.gke.io/gcp-service-account"]
|
||||||
|
value: jellyfin@my-project.iam.gserviceaccount.com
|
||||||
|
|
||||||
|
# Scenario: Minimal security (no automount)
|
||||||
|
- it: should support minimal security setup without token automount
|
||||||
|
set:
|
||||||
|
serviceAccount.create: true
|
||||||
|
serviceAccount.automount: false
|
||||||
|
asserts:
|
||||||
|
- template: serviceaccount.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.automountServiceAccountToken
|
||||||
|
value: false
|
||||||
@@ -0,0 +1,482 @@
|
|||||||
|
suite: test servicemonitor
|
||||||
|
templates:
|
||||||
|
- serviceMonitor.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# DEFAULT BEHAVIOR
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor by default
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor when only metrics.enabled
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor when only serviceMonitor.enabled
|
||||||
|
set:
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create ServiceMonitor when both metrics and serviceMonitor enabled
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isKind:
|
||||||
|
of: ServiceMonitor
|
||||||
|
- isAPIVersion:
|
||||||
|
of: monitoring.coreos.com/v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# BASIC CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have correct default name
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.name
|
||||||
|
value: RELEASE-NAME-jellyfin
|
||||||
|
|
||||||
|
- it: should not set namespace by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: metadata.namespace
|
||||||
|
|
||||||
|
- it: should use custom namespace when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.namespace: monitoring
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.namespace
|
||||||
|
value: monitoring
|
||||||
|
|
||||||
|
- it: should have correct selector labels
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: spec.selector.matchLabels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LABELS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have custom labels by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
|
||||||
|
- it: should add custom labels when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.labels:
|
||||||
|
prometheus: kube-prometheus
|
||||||
|
team: platform
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels.prometheus
|
||||||
|
value: kube-prometheus
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels.team
|
||||||
|
value: platform
|
||||||
|
|
||||||
|
- it: should merge custom labels with default labels
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.labels:
|
||||||
|
custom: label
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels.custom
|
||||||
|
value: label
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ENDPOINT CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should have default endpoint configuration
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints
|
||||||
|
count: 1
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].port
|
||||||
|
value: http
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].path
|
||||||
|
value: /metrics
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].scheme
|
||||||
|
value: http
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 30s
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].scrapeTimeout
|
||||||
|
value: 30s
|
||||||
|
|
||||||
|
- it: should set custom interval
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.interval: 60s
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 60s
|
||||||
|
|
||||||
|
- it: should set custom scrapeTimeout
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.scrapeTimeout: 15s
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].scrapeTimeout
|
||||||
|
value: 15s
|
||||||
|
|
||||||
|
- it: should set custom path
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.path: /custom/metrics
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].path
|
||||||
|
value: /custom/metrics
|
||||||
|
|
||||||
|
- it: should set custom scheme
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.scheme: https
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].scheme
|
||||||
|
value: https
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TLS CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have tlsConfig by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.endpoints[0].tlsConfig
|
||||||
|
|
||||||
|
- it: should add tlsConfig when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.tlsConfig:
|
||||||
|
insecureSkipVerify: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].tlsConfig.insecureSkipVerify
|
||||||
|
value: true
|
||||||
|
|
||||||
|
- it: should support complex tlsConfig
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.tlsConfig:
|
||||||
|
ca:
|
||||||
|
secret:
|
||||||
|
name: prometheus-tls-ca
|
||||||
|
key: ca.crt
|
||||||
|
cert:
|
||||||
|
secret:
|
||||||
|
name: prometheus-tls-cert
|
||||||
|
key: tls.crt
|
||||||
|
keySecret:
|
||||||
|
name: prometheus-tls-key
|
||||||
|
key: tls.key
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].tlsConfig.ca.secret.name
|
||||||
|
value: prometheus-tls-ca
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].tlsConfig.cert.secret.name
|
||||||
|
value: prometheus-tls-cert
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RELABELING
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have relabelings by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.endpoints[0].relabelings
|
||||||
|
|
||||||
|
- it: should add relabelings when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.relabelings:
|
||||||
|
- sourceLabels: [__meta_kubernetes_pod_name]
|
||||||
|
targetLabel: pod
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints[0].relabelings
|
||||||
|
count: 1
|
||||||
|
- contains:
|
||||||
|
path: spec.endpoints[0].relabelings[0].sourceLabels
|
||||||
|
content: __meta_kubernetes_pod_name
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].relabelings[0].targetLabel
|
||||||
|
value: pod
|
||||||
|
|
||||||
|
- it: should support multiple relabelings
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.relabelings:
|
||||||
|
- sourceLabels: [__meta_kubernetes_pod_name]
|
||||||
|
targetLabel: pod
|
||||||
|
- sourceLabels: [__meta_kubernetes_namespace]
|
||||||
|
targetLabel: namespace
|
||||||
|
- action: drop
|
||||||
|
regex: temp.*
|
||||||
|
sourceLabels: [__meta_kubernetes_pod_label_temp]
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints[0].relabelings
|
||||||
|
count: 3
|
||||||
|
|
||||||
|
- it: should not have metricRelabelings by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.endpoints[0].metricRelabelings
|
||||||
|
|
||||||
|
- it: should add metricRelabelings when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.metricRelabelings:
|
||||||
|
- sourceLabels: [__name__]
|
||||||
|
regex: 'http_.*'
|
||||||
|
action: keep
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints[0].metricRelabelings
|
||||||
|
count: 1
|
||||||
|
- contains:
|
||||||
|
path: spec.endpoints[0].metricRelabelings[0].sourceLabels
|
||||||
|
content: __name__
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].metricRelabelings[0].action
|
||||||
|
value: keep
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TARGET LABELS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not have targetLabels by default
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.targetLabels
|
||||||
|
|
||||||
|
- it: should add targetLabels when specified
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.targetLabels:
|
||||||
|
- app
|
||||||
|
- version
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.targetLabels
|
||||||
|
count: 2
|
||||||
|
- contains:
|
||||||
|
path: spec.targetLabels
|
||||||
|
content: app
|
||||||
|
- contains:
|
||||||
|
path: spec.targetLabels
|
||||||
|
content: version
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# EDGE CASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Interval format validation (these should work with Prometheus)
|
||||||
|
- it: should accept various interval formats
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.interval: 1m
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 1m
|
||||||
|
|
||||||
|
- it: should accept interval in hours
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.interval: 1h
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 1h
|
||||||
|
|
||||||
|
# Empty configurations
|
||||||
|
- it: should handle empty labels object
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.labels: {}
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
|
||||||
|
- it: should handle empty relabelings array
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.relabelings: []
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.endpoints[0].relabelings
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLETE SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create complete ServiceMonitor with all features
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
metrics.serviceMonitor.namespace: monitoring
|
||||||
|
metrics.serviceMonitor.labels:
|
||||||
|
prometheus: kube-prometheus
|
||||||
|
metrics.serviceMonitor.interval: 1m
|
||||||
|
metrics.serviceMonitor.scrapeTimeout: 30s
|
||||||
|
metrics.serviceMonitor.path: /metrics
|
||||||
|
metrics.serviceMonitor.port: 8096
|
||||||
|
metrics.serviceMonitor.scheme: https
|
||||||
|
metrics.serviceMonitor.tlsConfig:
|
||||||
|
insecureSkipVerify: true
|
||||||
|
metrics.serviceMonitor.relabelings:
|
||||||
|
- sourceLabels: [__meta_kubernetes_pod_name]
|
||||||
|
targetLabel: pod
|
||||||
|
metrics.serviceMonitor.metricRelabelings:
|
||||||
|
- action: drop
|
||||||
|
regex: 'unnecessary_metric_.*'
|
||||||
|
sourceLabels: [__name__]
|
||||||
|
metrics.serviceMonitor.targetLabels:
|
||||||
|
- app
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.namespace
|
||||||
|
value: monitoring
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels.prometheus
|
||||||
|
value: kube-prometheus
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].interval
|
||||||
|
value: 1m
|
||||||
|
- equal:
|
||||||
|
path: spec.endpoints[0].scheme
|
||||||
|
value: https
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.endpoints[0].tlsConfig
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints[0].relabelings
|
||||||
|
count: 1
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints[0].metricRelabelings
|
||||||
|
count: 1
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.targetLabels
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should always use monitoring.coreos.com/v1 API version
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: apiVersion
|
||||||
|
value: monitoring.coreos.com/v1
|
||||||
|
|
||||||
|
- it: should always have correct chart labels
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/name"]
|
||||||
|
value: jellyfin
|
||||||
|
- equal:
|
||||||
|
path: metadata.labels["app.kubernetes.io/instance"]
|
||||||
|
value: RELEASE-NAME
|
||||||
|
|
||||||
|
- it: should always have exactly one endpoint
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.endpoints
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# Verify dependency on metrics.enabled
|
||||||
|
- it: should require both metrics.enabled and serviceMonitor.enabled
|
||||||
|
set:
|
||||||
|
metrics.enabled: false
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
suite: test validation and mutual exclusions
|
||||||
|
templates:
|
||||||
|
- deployment.yaml
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- serviceMonitor.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- httproute.yaml
|
||||||
|
- persistentVolumeClaim.yaml
|
||||||
|
tests:
|
||||||
|
# =============================================================================
|
||||||
|
# NETWORKPOLICY MUTUAL EXCLUSIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should fail when NetworkPolicy enabled with DLNA
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should fail when NetworkPolicy enabled with hostNetwork
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should fail when NetworkPolicy enabled with both DLNA and hostNetwork
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should succeed when NetworkPolicy enabled without DLNA or hostNetwork
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: false
|
||||||
|
podPrivileges.hostNetwork: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should succeed when NetworkPolicy disabled with DLNA
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: false
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SERVICEMONITOR DEPENDENCIES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor when only metrics.enabled
|
||||||
|
template: serviceMonitor.yaml
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: false
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should not create ServiceMonitor when only serviceMonitor.enabled
|
||||||
|
template: serviceMonitor.yaml
|
||||||
|
set:
|
||||||
|
metrics.enabled: false
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create ServiceMonitor when both enabled
|
||||||
|
template: serviceMonitor.yaml
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DLNA AND HOSTNETWORK SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should enable hostNetwork automatically when DLNA enabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
|
||||||
|
- it: should not enable hostNetwork when DLNA disabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: false
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should allow hostNetwork without DLNA
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: false
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REPLICACOUNT VALIDATION (WARNING, NOT ERROR)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Note: Jellyfin doesn't support horizontal scaling, but chart allows it
|
||||||
|
# Users should be warned but not prevented from setting replicaCount > 1
|
||||||
|
|
||||||
|
- it: should allow replicaCount of 1 (recommended)
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
replicaCount: 1
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 1
|
||||||
|
|
||||||
|
- it: should allow replicaCount > 1 (not recommended but not forbidden)
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
replicaCount: 3
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.replicas
|
||||||
|
value: 3
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PERSISTENCE VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# These are handled by values.schema.json, testing that templates work correctly
|
||||||
|
|
||||||
|
- it: should handle persistence.media.type=pvc without errors
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: RELEASE-NAME-jellyfin-media
|
||||||
|
|
||||||
|
- it: should handle persistence.media.type=hostPath with path
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: hostPath
|
||||||
|
persistence.media.hostPath: /mnt/media
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/media
|
||||||
|
type: Directory
|
||||||
|
|
||||||
|
- it: should handle persistence.media.type=emptyDir
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: emptyDir
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
emptyDir: {}
|
||||||
|
|
||||||
|
# existingClaim should not create PVC but should mount it
|
||||||
|
- it: should not create PVC when existingClaim is used
|
||||||
|
template: persistentVolumeClaim.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.existingClaim: my-existing-claim
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1 # only config, not media
|
||||||
|
|
||||||
|
- it: should mount existingClaim in deployment
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
persistence.media.type: pvc
|
||||||
|
persistence.media.existingClaim: my-existing-claim
|
||||||
|
asserts:
|
||||||
|
- contains:
|
||||||
|
path: spec.template.spec.volumes
|
||||||
|
content:
|
||||||
|
name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: my-existing-claim
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INGRESS VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should allow ingress with empty hosts (edge case, likely misconfiguration)
|
||||||
|
template: ingress.yaml
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts: []
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- lengthEqual:
|
||||||
|
path: spec.rules
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should allow ingress with hosts
|
||||||
|
template: ingress.yaml
|
||||||
|
set:
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- isNotNull:
|
||||||
|
path: spec.rules
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# HTTPROUTE VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not create HTTPRoute by default
|
||||||
|
template: httproute.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should create HTTPRoute when enabled
|
||||||
|
template: httproute.yaml
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should allow HTTPRoute with empty parentRefs (edge case)
|
||||||
|
template: httproute.yaml
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.parentRefs: []
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- it: should allow HTTPRoute with empty hostnames (edge case)
|
||||||
|
template: httproute.yaml
|
||||||
|
set:
|
||||||
|
httpRoute.enabled: true
|
||||||
|
httpRoute.hostnames: []
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SERVICE TYPE VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should create service with ClusterIP type
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: ClusterIP
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: ClusterIP
|
||||||
|
|
||||||
|
- it: should create service with NodePort type
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: NodePort
|
||||||
|
|
||||||
|
- it: should create service with LoadBalancer type
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: LoadBalancer
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.type
|
||||||
|
value: LoadBalancer
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PORT RANGE VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# These are validated by values.schema.json, testing templates work correctly
|
||||||
|
|
||||||
|
- it: should accept service.port in valid range (1-65535)
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.port: 8096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].port
|
||||||
|
value: 8096
|
||||||
|
|
||||||
|
- it: should accept nodePort in valid range (30000-32767)
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 30096
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 30096
|
||||||
|
|
||||||
|
- it: should accept nodePort at lower boundary
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 30000
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 30000
|
||||||
|
|
||||||
|
- it: should accept nodePort at upper boundary
|
||||||
|
template: service.yaml
|
||||||
|
set:
|
||||||
|
service.type: NodePort
|
||||||
|
service.nodePort: 32767
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.ports[0].nodePort
|
||||||
|
value: 32767
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DNS POLICY VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should not set dnsPolicy by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
|
||||||
|
- it: should use ClusterFirstWithHostNet when hostNetwork enabled
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: ClusterFirstWithHostNet
|
||||||
|
|
||||||
|
- it: should allow custom dnsPolicy override
|
||||||
|
template: deployment.yaml
|
||||||
|
set:
|
||||||
|
dnsPolicy: None
|
||||||
|
asserts:
|
||||||
|
- equal:
|
||||||
|
path: spec.template.spec.dnsPolicy
|
||||||
|
value: None
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COMPLEX VALIDATION SCENARIOS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should fail with all conflicting options enabled
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
set:
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
podPrivileges.hostNetwork: true
|
||||||
|
asserts:
|
||||||
|
- failedTemplate: {}
|
||||||
|
|
||||||
|
- it: should succeed with compatible options
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
jellyfin.enableDLNA: false
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
persistence.media.type: pvc
|
||||||
|
service.type: LoadBalancer
|
||||||
|
asserts:
|
||||||
|
- template: serviceMonitor.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: deployment.yaml
|
||||||
|
isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should configure for DLNA without NetworkPolicy
|
||||||
|
set:
|
||||||
|
jellyfin.enableDLNA: true
|
||||||
|
networkPolicy.enabled: false
|
||||||
|
asserts:
|
||||||
|
- template: deployment.yaml
|
||||||
|
equal:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
value: true
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# REGRESSION TESTS FOR VALIDATIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- it: should maintain hostNetwork=false by default
|
||||||
|
template: deployment.yaml
|
||||||
|
asserts:
|
||||||
|
- isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
|
|
||||||
|
- it: should maintain NetworkPolicy disabled by default
|
||||||
|
template: networkpolicy.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should maintain ServiceMonitor disabled by default
|
||||||
|
template: serviceMonitor.yaml
|
||||||
|
asserts:
|
||||||
|
- hasDocuments:
|
||||||
|
count: 0
|
||||||
|
|
||||||
|
- it: should allow all features when properly configured
|
||||||
|
set:
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.serviceMonitor.enabled: true
|
||||||
|
networkPolicy.enabled: true
|
||||||
|
jellyfin.enableDLNA: false
|
||||||
|
ingress.enabled: true
|
||||||
|
ingress.hosts:
|
||||||
|
- host: jellyfin.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
asserts:
|
||||||
|
- template: serviceMonitor.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: networkpolicy.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: ingress.yaml
|
||||||
|
hasDocuments:
|
||||||
|
count: 1
|
||||||
|
- template: deployment.yaml
|
||||||
|
isNull:
|
||||||
|
path: spec.template.spec.hostNetwork
|
||||||
@@ -0,0 +1,814 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Jellyfin Helm Chart Values",
|
||||||
|
"description": "JSON Schema for Jellyfin Helm chart values.yaml with comprehensive validation and mutual exclusivity checks",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"replicaCount": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"default": 1,
|
||||||
|
"description": "Number of Jellyfin replicas. Should remain at 1 as Jellyfin does not support horizontal scaling."
|
||||||
|
},
|
||||||
|
"revisionHistoryLimit": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"default": 3,
|
||||||
|
"description": "Number of old ReplicaSets to retain for rollback history. Set to 0 to disable (not recommended)."
|
||||||
|
},
|
||||||
|
"imagePullSecrets": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the secret containing registry credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Image pull secrets for authenticating with private container registries"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["repository", "pullPolicy"],
|
||||||
|
"properties": {
|
||||||
|
"repository": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "docker.io/jellyfin/jellyfin",
|
||||||
|
"pattern": "^[a-z0-9.-]+(:[0-9]+)?(/[a-z0-9._-]+)*$",
|
||||||
|
"description": "Container image repository for Jellyfin"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Image tag. Leave empty to use appVersion from Chart.yaml"
|
||||||
|
},
|
||||||
|
"pullPolicy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["Always", "IfNotPresent", "Never"],
|
||||||
|
"default": "IfNotPresent",
|
||||||
|
"description": "Image pull policy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nameOverride": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Override the chart name in resource names"
|
||||||
|
},
|
||||||
|
"fullnameOverride": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Override the full resource name (namespace-name)"
|
||||||
|
},
|
||||||
|
"serviceAccount": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"create": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Create a dedicated service account"
|
||||||
|
},
|
||||||
|
"automount": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Automatically mount service account token"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Annotations to add to the service account"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Custom name for service account. Auto-generated if empty."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"podAnnotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Annotations to add to Jellyfin pods"
|
||||||
|
},
|
||||||
|
"podLabels": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Additional labels for Jellyfin pods"
|
||||||
|
},
|
||||||
|
"podSecurityContext": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Security context for the pod (fsGroup, runAsUser, etc.)"
|
||||||
|
},
|
||||||
|
"securityContext": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Security context for the container (capabilities, readOnlyRootFilesystem, etc.)"
|
||||||
|
},
|
||||||
|
"runtimeClassName": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "RuntimeClass for the pod (e.g., nvidia, kata)"
|
||||||
|
},
|
||||||
|
"priorityClassName": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "PriorityClass name for pod scheduling priority"
|
||||||
|
},
|
||||||
|
"dnsConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Custom DNS configuration for the pod"
|
||||||
|
},
|
||||||
|
"dnsPolicy": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"enum": ["", "ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"],
|
||||||
|
"description": "DNS policy for the pod"
|
||||||
|
},
|
||||||
|
"deploymentStrategy": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["RollingUpdate", "Recreate"],
|
||||||
|
"default": "RollingUpdate",
|
||||||
|
"description": "Deployment update strategy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deploymentAnnotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Annotations for the Deployment resource"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"],
|
||||||
|
"default": "ClusterIP",
|
||||||
|
"description": "Kubernetes service type"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 65535,
|
||||||
|
"default": 8096,
|
||||||
|
"description": "Service port for Jellyfin web interface"
|
||||||
|
},
|
||||||
|
"nodePort": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 30000,
|
||||||
|
"maximum": 32767,
|
||||||
|
"description": "NodePort when service type is NodePort"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Service annotations"
|
||||||
|
},
|
||||||
|
"ipFamilyPolicy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["", "SingleStack", "PreferDualStack", "RequireDualStack"],
|
||||||
|
"default": "",
|
||||||
|
"description": "IP family policy for dual-stack support"
|
||||||
|
},
|
||||||
|
"ipFamilies": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["IPv4", "IPv6"]
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"maxItems": 2,
|
||||||
|
"default": [],
|
||||||
|
"description": "IP families (IPv4, IPv6, or both)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ingress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable Ingress resource"
|
||||||
|
},
|
||||||
|
"className": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "IngressClass name (e.g., nginx, traefik)"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Ingress annotations"
|
||||||
|
},
|
||||||
|
"hosts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["host"],
|
||||||
|
"properties": {
|
||||||
|
"host": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "hostname",
|
||||||
|
"description": "Hostname for Ingress"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL path"
|
||||||
|
},
|
||||||
|
"pathType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["Prefix", "Exact", "ImplementationSpecific"],
|
||||||
|
"description": "Path matching type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"httpRoute": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable Gateway API HTTPRoute"
|
||||||
|
},
|
||||||
|
"parentRefs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hostnames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "hostname"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "CPU/Memory resource requests and limits"
|
||||||
|
},
|
||||||
|
"startupProbe": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"default": null,
|
||||||
|
"description": "Startup probe configuration. Useful for large media libraries with slow startup."
|
||||||
|
},
|
||||||
|
"livenessProbe": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Liveness probe configuration"
|
||||||
|
},
|
||||||
|
"readinessProbe": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Readiness probe configuration"
|
||||||
|
},
|
||||||
|
"volumes": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional volumes for the pod"
|
||||||
|
},
|
||||||
|
"volumeMounts": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional volume mounts for the container"
|
||||||
|
},
|
||||||
|
"nodeSelector": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Node labels for pod assignment"
|
||||||
|
},
|
||||||
|
"tolerations": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Tolerations for pod assignment"
|
||||||
|
},
|
||||||
|
"affinity": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Affinity rules for pod assignment"
|
||||||
|
},
|
||||||
|
"podPrivileges": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"hostIPC": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable hostIPC namespace. Required for NVIDIA MPS GPU sharing."
|
||||||
|
},
|
||||||
|
"hostNetwork": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable hostNetwork. Required for DLNA. Mutually exclusive with NetworkPolicy."
|
||||||
|
},
|
||||||
|
"hostPID": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable hostPID namespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jellyfin": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enableDLNA": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable DLNA support. Requires hostNetwork=true. Mutually exclusive with NetworkPolicy."
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Override container entrypoint"
|
||||||
|
},
|
||||||
|
"args": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Additional arguments for entrypoint"
|
||||||
|
},
|
||||||
|
"envFrom": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"configMapRef": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"secretRef": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prefix": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Load environment variables from ConfigMap or Secret"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"valueFrom": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Additional environment variables"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"persistence": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable persistent storage for config. False uses emptyDir."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["pvc", "hostPath", "emptyDir"],
|
||||||
|
"default": "pvc",
|
||||||
|
"description": "Storage type: pvc, hostPath, or emptyDir"
|
||||||
|
},
|
||||||
|
"accessMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod"],
|
||||||
|
"default": "ReadWriteOnce",
|
||||||
|
"description": "PVC access mode"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(\\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei|K|M|G|T|P|E)?$",
|
||||||
|
"default": "5Gi",
|
||||||
|
"description": "PVC size (e.g., 5Gi, 10Gi)"
|
||||||
|
},
|
||||||
|
"storageClass": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "StorageClass name. Empty uses default provisioner."
|
||||||
|
},
|
||||||
|
"existingClaim": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Use existing PVC instead of creating new"
|
||||||
|
},
|
||||||
|
"hostPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host path when type is hostPath"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "PVC annotations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable persistent storage for media"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["pvc", "hostPath", "emptyDir"],
|
||||||
|
"default": "pvc",
|
||||||
|
"description": "Storage type: pvc, hostPath, or emptyDir"
|
||||||
|
},
|
||||||
|
"accessMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod"],
|
||||||
|
"default": "ReadWriteOnce",
|
||||||
|
"description": "PVC access mode"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(\\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei|K|M|G|T|P|E)?$",
|
||||||
|
"default": "25Gi",
|
||||||
|
"description": "PVC size"
|
||||||
|
},
|
||||||
|
"storageClass": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "StorageClass name"
|
||||||
|
},
|
||||||
|
"existingClaim": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Use existing PVC"
|
||||||
|
},
|
||||||
|
"hostPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host path when type is hostPath"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "PVC annotations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable dedicated cache volume for better performance"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["pvc", "hostPath", "emptyDir"],
|
||||||
|
"default": "pvc",
|
||||||
|
"description": "Storage type"
|
||||||
|
},
|
||||||
|
"accessMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ReadWriteOnce", "ReadOnlyMany", "ReadWriteMany", "ReadWriteOncePod"],
|
||||||
|
"default": "ReadWriteOnce",
|
||||||
|
"description": "PVC access mode"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(\\.[0-9]+)?(Ki|Mi|Gi|Ti|Pi|Ei|K|M|G|T|P|E)?$",
|
||||||
|
"default": "10Gi",
|
||||||
|
"description": "PVC size"
|
||||||
|
},
|
||||||
|
"storageClass": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "StorageClass name (can use faster storage)"
|
||||||
|
},
|
||||||
|
"existingClaim": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Use existing PVC"
|
||||||
|
},
|
||||||
|
"hostPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host path when type is hostPath"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "PVC annotations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable metrics collection"
|
||||||
|
},
|
||||||
|
"serviceMonitor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Create Prometheus ServiceMonitor"
|
||||||
|
},
|
||||||
|
"interval": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(s|m|h)$",
|
||||||
|
"description": "Scrape interval (e.g., 30s, 1m)"
|
||||||
|
},
|
||||||
|
"scrapeTimeout": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(s|m|h)$",
|
||||||
|
"description": "Scrape timeout"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Additional labels for ServiceMonitor"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "ServiceMonitor annotations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"initContainers": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "DEPRECATED: Use extraInitContainers instead. Will be removed after 2030."
|
||||||
|
},
|
||||||
|
"extraInitContainers": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional init containers"
|
||||||
|
},
|
||||||
|
"extraContainers": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional sidecar containers"
|
||||||
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable NetworkPolicy for network isolation. Mutually exclusive with hostNetwork and DLNA."
|
||||||
|
},
|
||||||
|
"policyTypes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["Ingress", "Egress"]
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1,
|
||||||
|
"default": ["Ingress", "Egress"],
|
||||||
|
"description": "Policy types to enforce"
|
||||||
|
},
|
||||||
|
"ingress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allowExternal": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Allow access from any pod/namespace. Set false for production."
|
||||||
|
},
|
||||||
|
"podSelector": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Pod selector for allowed ingress (when allowExternal=false)"
|
||||||
|
},
|
||||||
|
"namespaceSelector": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"description": "Namespace selector for allowed ingress"
|
||||||
|
},
|
||||||
|
"customRules": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional custom ingress rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allowDNS": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Allow DNS resolution. Highly recommended."
|
||||||
|
},
|
||||||
|
"dnsNamespace": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "kube-system",
|
||||||
|
"description": "Namespace where DNS service runs"
|
||||||
|
},
|
||||||
|
"dnsPodSelector": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {
|
||||||
|
"k8s-app": "kube-dns"
|
||||||
|
},
|
||||||
|
"description": "Pod selector for DNS pods"
|
||||||
|
},
|
||||||
|
"allowAllEgress": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Allow all internet access for metadata/subtitles"
|
||||||
|
},
|
||||||
|
"restrictedEgress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allowMetadata": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Allow HTTPS/443 for metadata providers"
|
||||||
|
},
|
||||||
|
"allowInCluster": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Allow pod-to-pod communication"
|
||||||
|
},
|
||||||
|
"allowedCIDRs": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$|^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/[0-9]{1,3}$",
|
||||||
|
"description": "CIDR notation (e.g., 10.0.0.0/8)"
|
||||||
|
},
|
||||||
|
"description": "Additional allowed IP ranges"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customRules": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "Additional custom egress rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Prometheus namespace. Empty uses same as Jellyfin."
|
||||||
|
},
|
||||||
|
"podSelector": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {
|
||||||
|
"app.kubernetes.io/name": "prometheus"
|
||||||
|
},
|
||||||
|
"description": "Prometheus pod selector"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"networkPolicy": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"const": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["enabled"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["networkPolicy"]
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"not": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"jellyfin": {
|
||||||
|
"properties": {
|
||||||
|
"enableDLNA": {
|
||||||
|
"const": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["enableDLNA"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["jellyfin"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"podPrivileges": {
|
||||||
|
"properties": {
|
||||||
|
"hostNetwork": {
|
||||||
|
"const": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["hostNetwork"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["podPrivileges"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+458
-95
@@ -1,143 +1,506 @@
|
|||||||
# -- Number of jellyfin replicas to start. Should be left at 1
|
# Default values for Jellyfin.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
|
||||||
|
# -- Number of Jellyfin replicas to start. Should be left at 1.
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
|
# -- Number of old ReplicaSets to retain for rollback history.
|
||||||
|
# Set to 0 to disable revision history (not recommended).
|
||||||
|
# If not specified, Kubernetes defaults to 10.
|
||||||
|
# See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
|
||||||
|
# -- Image pull secrets to authenticate with private repositories.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||||
|
imagePullSecrets: []
|
||||||
|
|
||||||
image:
|
image:
|
||||||
# -- Set cutom repository for jellyfin image
|
# -- Container image repository for Jellyfin.
|
||||||
repository: docker.io/jellyfin/jellyfin
|
repository: docker.io/jellyfin/jellyfin
|
||||||
# -- Set jellyfin version which should be required since chart is updated with new versions automatically
|
# -- Jellyfin container image tag. Leave empty to automatically use the Chart's app version.
|
||||||
tag: ""
|
tag: ""
|
||||||
|
# -- Image pull policy (Always, IfNotPresent, or Never).
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
# -- Override the default name of the chart.
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
# -- Override the default full name of the chart.
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
|
|
||||||
# -- Setting this to true enables DLNA which requires the pod to be attached to the
|
# -- Service account configuration. See: https://kubernetes.io/docs/concepts/security/service-accounts/
|
||||||
# host network in order to be useful - this can break things like ingress to the service
|
serviceAccount:
|
||||||
# https://jellyfin.org/docs/general/networking/dlna.html
|
# -- Specifies whether to create a service account.
|
||||||
enableDLNA: false
|
create: true
|
||||||
|
# -- Automatically mount API credentials for the service account.
|
||||||
|
automount: true
|
||||||
|
# -- Annotations for the service account.
|
||||||
|
annotations: {}
|
||||||
|
# -- Custom name for the service account. If left empty, the name will be autogenerated.
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
# -- Annotations to add to the pod.
|
||||||
|
podAnnotations: {}
|
||||||
|
# -- Additional labels to add to the pod.
|
||||||
|
podLabels: {}
|
||||||
|
|
||||||
|
# -- Security context for the pod.
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
# -- Security context for the container.
|
||||||
|
securityContext: {}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
# -- Define a custom runtimeClassName for the pod.
|
||||||
|
runtimeClassName: ''
|
||||||
|
|
||||||
|
# -- Define a priorityClassName for the pod.
|
||||||
|
priorityClassName: ""
|
||||||
|
|
||||||
|
# -- Define a dnsConfig. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config
|
||||||
|
# Use this to provide a custom DNS resolver configuration
|
||||||
|
dnsConfig: {}
|
||||||
|
# nameservers:
|
||||||
|
# - 192.0.2.1
|
||||||
|
# searches:
|
||||||
|
# - ns1.svc.cluster-domain.example
|
||||||
|
# - my.dns.search.suffix
|
||||||
|
# options:
|
||||||
|
# - name: ndots
|
||||||
|
# value: "2"
|
||||||
|
# - name: edns0
|
||||||
|
|
||||||
|
# -- Define a dnsPolicy. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||||
|
dnsPolicy: ""
|
||||||
|
|
||||||
|
# -- Deployment strategy configuration. See `kubectl explain deployment.spec.strategy`.
|
||||||
|
deploymentStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
|
||||||
|
# -- Annotations to add to the deployment.
|
||||||
|
deploymentAnnotations: {}
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
# # -- Custom name for the service port
|
||||||
|
# name: http
|
||||||
|
# -- Service type (ClusterIP, NodePort, or LoadBalancer).
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
# -- Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/
|
||||||
|
# Options: SingleStack, PreferDualStack, RequireDualStack
|
||||||
|
# For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"]
|
||||||
|
# For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"]
|
||||||
|
ipFamilyPolicy: ""
|
||||||
|
# -- Supported IP families (IPv4, IPv6).
|
||||||
|
# Examples:
|
||||||
|
# IPv4 only: ["IPv4"]
|
||||||
|
# IPv6 only: ["IPv6"]
|
||||||
|
# Dual-stack (IPv4 primary): ["IPv4", "IPv6"]
|
||||||
|
# Dual-stack (IPv6 primary): ["IPv6", "IPv4"]
|
||||||
|
# Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket)
|
||||||
|
ipFamilies: []
|
||||||
|
# -- Specific IP address for the LoadBalancer.
|
||||||
|
loadBalancerIP: ""
|
||||||
|
# -- Class of the LoadBalancer.
|
||||||
|
loadBalancerClass: ""
|
||||||
|
# -- Source ranges allowed to access the LoadBalancer.
|
||||||
|
loadBalancerSourceRanges: []
|
||||||
|
# -- Port for the Jellyfin service.
|
||||||
port: 8096
|
port: 8096
|
||||||
## Specify the nodePort value for the LoadBalancer and NodePort service types.
|
# -- Name of the port in the service.
|
||||||
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
|
portName: service
|
||||||
##
|
# -- Annotations for the service.
|
||||||
# nodePort:
|
|
||||||
## Provide any additional annotations which may be required. This can be used to
|
|
||||||
## set the LoadBalancer service type to internal only.
|
|
||||||
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer
|
|
||||||
##
|
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
# -- Labels for the service.
|
||||||
labels: {}
|
labels: {}
|
||||||
## Use loadBalancerIP to request a specific static IP,
|
# -- External traffic policy (Cluster or Local).
|
||||||
## otherwise leave blank
|
|
||||||
##
|
|
||||||
loadBalancerIP:
|
|
||||||
# loadBalancerSourceRanges: []
|
|
||||||
## Set the externalTrafficPolicy in the Service to either Cluster or Local
|
|
||||||
# externalTrafficPolicy: Cluster
|
# externalTrafficPolicy: Cluster
|
||||||
|
# -- NodePort for the service (if applicable).
|
||||||
|
# nodePort:
|
||||||
|
|
||||||
|
# -- Ingress configuration. See: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
||||||
ingress:
|
ingress:
|
||||||
enabled: false
|
enabled: false
|
||||||
labels: {}
|
className: ""
|
||||||
annotations: {}
|
annotations: {}
|
||||||
# kubernetes.io/ingress.class: nginx
|
# kubernetes.io/ingress.class: nginx
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
path: /
|
|
||||||
hosts:
|
hosts:
|
||||||
- chart-example.local
|
- host: chart-example.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
tls: []
|
tls: []
|
||||||
# - secretName: chart-example-tls
|
# - secretName: chart-example-tls
|
||||||
# hosts:
|
# hosts:
|
||||||
# - chart-example.local
|
# - chart-example.local
|
||||||
|
|
||||||
persistence:
|
# -- HTTPRoute configuration for Gateway API. See: https://gateway-api.sigs.k8s.io/
|
||||||
config:
|
httpRoute:
|
||||||
enabled: false
|
enabled: false
|
||||||
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
|
annotations: {}
|
||||||
storageClass: "-"
|
# -- Gateway references to attach this HTTPRoute to
|
||||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
parentRefs: []
|
||||||
## the existingClaim variable
|
# - name: my-gateway
|
||||||
# existingClaim: your-claim
|
# namespace: gateway-system
|
||||||
# subPath: some-subpath
|
# sectionName: https
|
||||||
accessMode: ReadWriteOnce
|
# -- Hostnames to match for this HTTPRoute
|
||||||
size: 1Gi
|
hostnames: []
|
||||||
media:
|
# - jellyfin.example.com
|
||||||
enabled: false
|
# -- Rules for routing traffic
|
||||||
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
|
rules:
|
||||||
storageClass: "-"
|
- matches:
|
||||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
- path:
|
||||||
## the existingClaim variable
|
type: PathPrefix
|
||||||
# existingClaim: your-claim
|
value: /
|
||||||
# subPath: some-subpath
|
|
||||||
accessMode: ReadWriteOnce
|
|
||||||
size: 10Gi
|
|
||||||
# -- Add aditional PVs/PVCs to our container. Check values.yaml to see an example.
|
|
||||||
extraExistingClaimMounts: []
|
|
||||||
# - name: external-mount
|
|
||||||
# mountPath: /srv/external-mount
|
|
||||||
## A manually managed Persistent Volume and Claim
|
|
||||||
## If defined, PVC must be created manually before volume will be bound
|
|
||||||
# existingClaim:
|
|
||||||
# readOnly: true
|
|
||||||
|
|
||||||
|
# -- Resource requests and limits for the Jellyfin container.
|
||||||
resources: {}
|
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:
|
# limits:
|
||||||
# cpu: 100m
|
# cpu: 100m
|
||||||
# memory: 128Mi
|
# memory: 128Mi
|
||||||
# requests:
|
# requests:
|
||||||
# cpu: 100m
|
# cpu: 100m
|
||||||
# memory: 128Mi
|
# memory: 128Mi
|
||||||
|
|
||||||
|
# -- Configure startup probe for Jellyfin.
|
||||||
|
# This probe gives Jellyfin enough time to start, especially with large media libraries.
|
||||||
|
# After the startup probe succeeds once, liveness and readiness probes take over.
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 30
|
||||||
|
# With these defaults, Jellyfin has up to 5 minutes (30 * 10s) to start.
|
||||||
|
# Adjust failureThreshold if you have very large media libraries or slow storage.
|
||||||
|
|
||||||
|
# -- Configure liveness probe for Jellyfin.
|
||||||
|
# This probe is disabled during startup (startup probe handles initial checks).
|
||||||
|
# Uses httpGet for compatibility with both IPv4 and IPv6.
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
# successThreshold: 1
|
||||||
|
# failureThreshold: 3
|
||||||
|
# timeoutSeconds: 1
|
||||||
|
# periodSeconds: 10
|
||||||
|
|
||||||
|
# -- Configure readiness probe for Jellyfin.
|
||||||
|
# This probe is disabled during startup (startup probe handles initial checks).
|
||||||
|
# Uses httpGet for compatibility with both IPv4 and IPv6.
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
# successThreshold: 1
|
||||||
|
# failureThreshold: 3
|
||||||
|
# timeoutSeconds: 1
|
||||||
|
# periodSeconds: 10
|
||||||
|
|
||||||
|
# -- Additional volumes to mount in the Jellyfin pod.
|
||||||
|
volumes: []
|
||||||
|
# - name: foo
|
||||||
|
# secret:
|
||||||
|
# secretName: mysecret
|
||||||
|
# optional: false
|
||||||
|
|
||||||
|
# -- Additional volume mounts for the Jellyfin container.
|
||||||
|
volumeMounts: []
|
||||||
|
# - name: foo
|
||||||
|
# mountPath: "/etc/foo"
|
||||||
|
# readOnly: true
|
||||||
|
|
||||||
|
# -- Node selector for pod scheduling.
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
|
|
||||||
|
# -- Tolerations for pod scheduling.
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
|
# -- Affinity rules for pod scheduling.
|
||||||
affinity: {}
|
affinity: {}
|
||||||
|
|
||||||
# -- Define additional volumes to mount to the pod.
|
# -- Privileged pod settings for advanced use cases
|
||||||
extraVolumes: []
|
podPrivileges:
|
||||||
#- name: renderD129
|
# -- Enable hostIPC namespace. Required for NVIDIA MPS (Multi-Process Service) GPU sharing. See: https://docs.nvidia.com/deploy/mps/index.html
|
||||||
# hostPath:
|
hostIPC: false
|
||||||
# path: /dev/dri/renderD129
|
# -- Enable hostNetwork. Allows pod to use the host's network namespace.
|
||||||
|
hostNetwork: false
|
||||||
|
# -- Enable hostPID namespace. Allows pod to see processes on the host.
|
||||||
|
hostPID: false
|
||||||
|
|
||||||
# -- Define mount points for additional volumes.
|
jellyfin:
|
||||||
extraVolumeMounts: []
|
# -- Enable DLNA. Requires host network. See: https://jellyfin.org/docs/general/networking/dlna.html
|
||||||
#- mountPath: /dev/dri/renderD129
|
enableDLNA: false
|
||||||
# name: renderD129
|
# -- Custom command to use as container entrypoint.
|
||||||
|
command: []
|
||||||
|
# -- Additional arguments for the entrypoint command.
|
||||||
|
args: []
|
||||||
|
# -- Load environment variables from ConfigMap or Secret.
|
||||||
|
# See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables
|
||||||
|
# Example:
|
||||||
|
# envFrom:
|
||||||
|
# - configMapRef:
|
||||||
|
# name: jellyfin-config
|
||||||
|
# - secretRef:
|
||||||
|
# name: jellyfin-secrets
|
||||||
|
envFrom: []
|
||||||
|
# -- Additional environment variables for the container.
|
||||||
|
# Example: Workaround for inotify limits (see Troubleshooting section in README)
|
||||||
|
# Example:
|
||||||
|
# env:
|
||||||
|
# - name: JELLYFIN_CACHE_DIR
|
||||||
|
# value: /cache
|
||||||
|
env: []
|
||||||
|
# - name: DOTNET_USE_POLLING_FILE_WATCHER
|
||||||
|
# value: "1"
|
||||||
|
|
||||||
# -- additional pod labels. Not used as a selector label.
|
persistence:
|
||||||
extraPodLabels: {}
|
config:
|
||||||
|
# -- set to false to use emptyDir
|
||||||
|
enabled: true
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 5Gi
|
||||||
|
# -- Custom annotations to be added to the PVC
|
||||||
|
annotations: {}
|
||||||
|
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
|
||||||
|
storageClass: ''
|
||||||
|
## -- Use an existing PVC for this mount
|
||||||
|
# existingClaim: ''
|
||||||
|
media:
|
||||||
|
# -- set to false to use emptyDir
|
||||||
|
enabled: true
|
||||||
|
# -- Type of volume for media storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting.
|
||||||
|
type: pvc
|
||||||
|
# -- Path on the host node for media storage, only used if type is 'hostPath'.
|
||||||
|
hostPath: ""
|
||||||
|
# -- PVC specific settings, only used if type is 'pvc'.
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 25Gi
|
||||||
|
# -- Custom annotations to be added to the PVC
|
||||||
|
annotations: {}
|
||||||
|
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
|
||||||
|
storageClass: ''
|
||||||
|
## -- Use an existing PVC for this mount
|
||||||
|
# existingClaim: ''
|
||||||
|
cache:
|
||||||
|
# -- set to false to use emptyDir
|
||||||
|
enabled: false
|
||||||
|
# -- Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting.
|
||||||
|
type: pvc
|
||||||
|
# -- Path on the host node for cache storage, only used if type is 'hostPath'.
|
||||||
|
hostPath: ""
|
||||||
|
# -- PVC specific settings, only used if type is 'pvc'.
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 10Gi
|
||||||
|
# -- Custom annotations to be added to the PVC
|
||||||
|
annotations: {}
|
||||||
|
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
|
||||||
|
storageClass: ''
|
||||||
|
## -- Use an existing PVC for this mount
|
||||||
|
# existingClaim: ''
|
||||||
|
|
||||||
# -- aditional environment variables passed to the pod
|
# -- Configuration for metrics collection and monitoring
|
||||||
extraEnvVars: []
|
metrics:
|
||||||
#- name: MY_ENV_VAR
|
# -- Enable or disable metrics collection
|
||||||
# value: my-env-var-value
|
enabled: false
|
||||||
|
# -- Configuration for the Prometheus ServiceMonitor
|
||||||
|
serviceMonitor:
|
||||||
|
# -- Enable or disable the creation of a ServiceMonitor resource
|
||||||
|
enabled: false
|
||||||
|
# -- Namespace where the ServiceMonitor resource should be created. Defaults to Release.Namespace
|
||||||
|
namespace: ''
|
||||||
|
# -- Labels to add to the ServiceMonitor resource
|
||||||
|
labels: {}
|
||||||
|
# -- Interval at which metrics should be scraped
|
||||||
|
interval: 30s
|
||||||
|
# -- Timeout for scraping metrics
|
||||||
|
scrapeTimeout: 30s
|
||||||
|
# -- Path to scrape for metrics
|
||||||
|
path: /metrics
|
||||||
|
# -- Port to scrape for metrics
|
||||||
|
port: 8096
|
||||||
|
# -- Scheme to use for scraping metrics (http or https)
|
||||||
|
scheme: http
|
||||||
|
# -- TLS configuration for scraping metrics
|
||||||
|
tlsConfig: {}
|
||||||
|
# -- Relabeling rules for the scraped metrics
|
||||||
|
relabelings: []
|
||||||
|
# -- Relabeling rules for the metrics before ingestion
|
||||||
|
metricRelabelings: []
|
||||||
|
# -- Target labels to add to the scraped metrics
|
||||||
|
targetLabels: []
|
||||||
|
|
||||||
# -- additional annotations applied to the pod
|
# -- DEPRECATED: Use extraInitContainers instead. Will be removed after 2030.
|
||||||
extraPodAnnotations: {}
|
# @deprecated - This parameter is deprecated, use extraInitContainers instead
|
||||||
|
initContainers: []
|
||||||
|
|
||||||
|
# -- Additional init containers to run inside the pod.
|
||||||
|
# Init containers run before the main application container starts.
|
||||||
|
# See: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
|
||||||
|
# Example:
|
||||||
|
# extraInitContainers:
|
||||||
|
# - name: init-config
|
||||||
|
# image: busybox:1.35
|
||||||
|
# command: ['sh', '-c', 'echo "Initializing..." && sleep 5']
|
||||||
|
# volumeMounts:
|
||||||
|
# - name: config
|
||||||
|
# mountPath: /config
|
||||||
|
extraInitContainers: []
|
||||||
|
|
||||||
# -- additional sidecar containers to run inside the pod.
|
# -- additional sidecar containers to run inside the pod.
|
||||||
extraContainers: {}
|
extraContainers: []
|
||||||
|
|
||||||
# -- Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds.
|
# -- Network Policy configuration for network isolation and security.
|
||||||
livenessProbe:
|
# Requires a CNI plugin that supports NetworkPolicy (Calico, Cilium, Weave, etc.).
|
||||||
enabled: true
|
# WARNING: NetworkPolicy cannot be enabled when hostNetwork is used (DLNA mode).
|
||||||
# failureThreshold: 3
|
# The chart will fail with an error if both are enabled simultaneously.
|
||||||
initialDelaySeconds: 10
|
networkPolicy:
|
||||||
# periodSeconds: 10
|
# -- Enable NetworkPolicy for the Jellyfin pod.
|
||||||
# successThreshold: 1
|
# By default, this is disabled to maintain backward compatibility.
|
||||||
# timeoutSeconds: 1
|
# When enabled, you can control which pods can access Jellyfin (ingress)
|
||||||
|
# and what external connections Jellyfin can make (egress).
|
||||||
|
enabled: false
|
||||||
|
|
||||||
# -- Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds.
|
# -- Policy types to enforce. Both ingress and egress policies can be enabled.
|
||||||
readinessProbe:
|
# See: https://kubernetes.io/docs/concepts/services-networking/network-policies/#policy-types
|
||||||
enabled: true
|
policyTypes:
|
||||||
# failureThreshold: 3
|
- Ingress
|
||||||
initialDelaySeconds: 10
|
- Egress
|
||||||
# periodSeconds: 10
|
|
||||||
# successThreshold: 1
|
# -- Ingress rules configuration - controls which pods/namespaces can access Jellyfin.
|
||||||
# timeoutSeconds: 1
|
ingress:
|
||||||
|
# -- Allow external access from any namespace and any pod.
|
||||||
|
# When true, any pod in the cluster can access Jellyfin (default behavior).
|
||||||
|
# When false, only pods matching podSelector/namespaceSelector can access.
|
||||||
|
# Set to false for production environments to restrict access.
|
||||||
|
allowExternal: true
|
||||||
|
|
||||||
|
# -- Custom pod selector for allowed ingress traffic.
|
||||||
|
# Only used when allowExternal is false.
|
||||||
|
# Allows you to specify which pods can access Jellyfin based on labels.
|
||||||
|
# Example - only allow pods with specific label:
|
||||||
|
# podSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# jellyfin-client: "true"
|
||||||
|
podSelector: {}
|
||||||
|
|
||||||
|
# -- Namespace selector to allow cross-namespace ingress.
|
||||||
|
# Only used when allowExternal is false.
|
||||||
|
# Allows you to specify which namespaces can access Jellyfin.
|
||||||
|
# Example - only allow from ingress-nginx namespace:
|
||||||
|
# namespaceSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# name: ingress-nginx
|
||||||
|
namespaceSelector: {}
|
||||||
|
|
||||||
|
# -- Additional custom ingress rules.
|
||||||
|
# Allows for complex scenarios not covered by the standard template.
|
||||||
|
# These rules are added as-is to the NetworkPolicy ingress section.
|
||||||
|
# Example - allow from monitoring namespace:
|
||||||
|
# customRules:
|
||||||
|
# - from:
|
||||||
|
# - namespaceSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# name: monitoring
|
||||||
|
# ports:
|
||||||
|
# - protocol: TCP
|
||||||
|
# port: 8096
|
||||||
|
customRules: []
|
||||||
|
|
||||||
|
# -- Egress rules configuration - controls what external connections Jellyfin can make.
|
||||||
|
egress:
|
||||||
|
# -- Allow DNS resolution (required for Jellyfin to function).
|
||||||
|
# This adds an egress rule for kube-system namespace with kube-dns pods.
|
||||||
|
# DNS is required for resolving metadata provider domains and subtitle services.
|
||||||
|
# It is highly recommended to keep this enabled.
|
||||||
|
allowDNS: true
|
||||||
|
|
||||||
|
# -- DNS namespace where DNS service is running.
|
||||||
|
# Usually "kube-system" but can differ in some Kubernetes distributions.
|
||||||
|
dnsNamespace: kube-system
|
||||||
|
|
||||||
|
# -- DNS pod selector labels.
|
||||||
|
# Default selector is for kube-dns, but CoreDNS or other DNS providers
|
||||||
|
# may use different labels. Adjust if needed.
|
||||||
|
# Common alternatives:
|
||||||
|
# k8s-app: kube-dns (default)
|
||||||
|
# k8s-app: coredns
|
||||||
|
# app.kubernetes.io/name: coredns
|
||||||
|
dnsPodSelector:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
|
||||||
|
# -- Allow all egress traffic (internet access for metadata, subtitles, images).
|
||||||
|
# When true, Jellyfin can connect to any external destination (0.0.0.0/0).
|
||||||
|
# This is the recommended default as Jellyfin needs internet access for:
|
||||||
|
# - Downloading movie/TV show metadata (TMDB, TheTVDB, OMDb)
|
||||||
|
# - Fetching poster images, fanart, and other artwork
|
||||||
|
# - Downloading subtitles (OpenSubtitles)
|
||||||
|
# - Updating plugins
|
||||||
|
# When false, you must configure restrictedEgress or customRules.
|
||||||
|
allowAllEgress: true
|
||||||
|
|
||||||
|
# -- Restricted egress mode for security-conscious deployments.
|
||||||
|
# Only used when allowAllEgress is false.
|
||||||
|
# Provides fine-grained control over outbound connections.
|
||||||
|
restrictedEgress:
|
||||||
|
# -- Allow HTTPS (443/TCP) for metadata providers.
|
||||||
|
# Most metadata providers (TMDB, TheTVDB, OpenSubtitles, Fanart.tv)
|
||||||
|
# use HTTPS, so this covers the majority of use cases.
|
||||||
|
# This allows connections to any IP on port 443.
|
||||||
|
allowMetadata: true
|
||||||
|
|
||||||
|
# -- Allow communication within the cluster (pod-to-pod).
|
||||||
|
# Useful if Jellyfin needs to connect to other services in the cluster.
|
||||||
|
# This allows connections to any pod in any namespace.
|
||||||
|
allowInCluster: true
|
||||||
|
|
||||||
|
# -- Additional IP CIDR blocks to allow egress.
|
||||||
|
# Useful for allowing specific IP ranges for metadata providers
|
||||||
|
# or other external services.
|
||||||
|
# Example - allow entire internet except private networks:
|
||||||
|
# allowedCIDRs:
|
||||||
|
# - 0.0.0.0/0
|
||||||
|
# Example - allow specific metadata provider IP ranges:
|
||||||
|
# allowedCIDRs:
|
||||||
|
# - 13.224.0.0/14 # CloudFront (used by many CDNs)
|
||||||
|
allowedCIDRs: []
|
||||||
|
|
||||||
|
# -- Additional custom egress rules.
|
||||||
|
# Allows for complex scenarios not covered by the standard template.
|
||||||
|
# These rules are added as-is to the NetworkPolicy egress section.
|
||||||
|
# Example - allow connections to specific database:
|
||||||
|
# customRules:
|
||||||
|
# - to:
|
||||||
|
# - podSelector:
|
||||||
|
# matchLabels:
|
||||||
|
# app: postgresql
|
||||||
|
# ports:
|
||||||
|
# - protocol: TCP
|
||||||
|
# port: 5432
|
||||||
|
customRules: []
|
||||||
|
|
||||||
|
# -- Prometheus metrics scraping configuration.
|
||||||
|
# Automatically allows ingress from Prometheus when metrics.serviceMonitor.enabled is true.
|
||||||
|
# This ensures Prometheus can scrape metrics without additional configuration.
|
||||||
|
metrics:
|
||||||
|
# -- Namespace where Prometheus is running.
|
||||||
|
# Leave empty to use the same namespace as Jellyfin.
|
||||||
|
# Set to the monitoring namespace if Prometheus is in a different namespace.
|
||||||
|
# Example: "monitoring" or "prometheus"
|
||||||
|
namespace: ""
|
||||||
|
|
||||||
|
# -- Pod selector for Prometheus pods.
|
||||||
|
# These labels must match your Prometheus deployment.
|
||||||
|
# The chart will automatically add an ingress rule for pods matching these labels.
|
||||||
|
# Default selector works with prometheus-operator and kube-prometheus-stack.
|
||||||
|
# Adjust if your Prometheus uses different labels.
|
||||||
|
podSelector:
|
||||||
|
app.kubernetes.io/name: prometheus
|
||||||
Reference in new issue
Block a user