Compare commits

...
150 Commits
55 changed files with 11459 additions and 489 deletions

No files matched your search

+9 -2
View File
@@ -1,6 +1,13 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>jellyfin/.github//renovate-presets/default"
"extends": ["github>jellyfin/.github//renovate-presets/default"],
"customManagers": [
{
"customType": "regex",
"fileMatch": ["charts/jellyfin/Chart.yaml"],
"matchStrings": ["appVersion: (?<currentValue>.*?)\\n"],
"datasourceTemplate": "docker",
"depNameTemplate": "jellyfin/jellyfin"
}
]
}
+2 -2
View File
@@ -17,12 +17,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: 3.x
+2 -2
View File
@@ -14,12 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
- name: Helm Docs GitHub Action
uses: losisin/helm-docs-github-action@57d2dc9379d9f0d5d22d2845a62c935d32cddfe2 # v1.3.3
uses: losisin/helm-docs-github-action@a57fae5676e4c55a228ea654a1bcaec8dd3cf5b5 # v1.6.2
with:
git-push: true
git-commit-message: "docs(charts): :memo: write updated reademe"
+93
View File
@@ -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'
+122 -3
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0
@@ -28,13 +28,132 @@ jobs:
git config --global user.email "team@jellyfin.org"
- name: Install Helm
uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
env:
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
uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
with:
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"
+25
View File
@@ -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
+21
View File
@@ -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
+2 -2
View File
@@ -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:
```plaintext
hellyfin-helm/
jellyfin-helm/
├── jellyfin/
│ ├── Chart.yaml
│ ├── values.yaml
@@ -32,7 +32,7 @@ hellyfin-helm/
To use the charts in this repository, first add the repository to Helm:
```bash
helm repo add jellyfin https://<your-repo-url>.git
helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm
helm repo update
```
+23
View File
@@ -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/
+49 -3
View File
@@ -1,8 +1,6 @@
apiVersion: v2
appVersion: 1.4.0
description: Jellyfin Media Server
name: jellyfin
version: 1.4.0
description: A Helm chart for Jellyfin Media Server
type: application
home: https://jellyfin.org/
icon: https://jellyfin.org/images/logo.svg
@@ -10,3 +8,51 @@ keywords:
- jellyfin
- media
- 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
+339
View File
@@ -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.
+45
View File
@@ -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.
+426 -78
View File
@@ -1,109 +1,186 @@
# jellyfin
![Version: 1.3.0](https://img.shields.io/badge/Version-1.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.9.11](https://img.shields.io/badge/AppVersion-10.9.11-informational?style=flat-square)
![Version: 3.0.0](https://img.shields.io/badge/Version-3.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.5](https://img.shields.io/badge/AppVersion-10.11.5-informational?style=flat-square)
Jellyfin Media Server
A Helm chart for Jellyfin Media Server
**Homepage:** <https://jellyfin.org/>
## Steps to Use a Helm Chart
### 1. Add a Helm Repository
Helm repositories contain collections of charts. You can add an existing repository using the following command:
## Quick Start
```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
To install a chart, use the following command:
For production deployments, create a custom `values.yaml` file with your configuration and install using:
```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:
```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
```
This chart is maintained by the [Jellyfin Project](https://jellyfin.org).
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | |
| 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 |
| affinity | object | `{}` | Affinity rules for pod scheduling. |
| deploymentAnnotations | object | `{}` | Annotations to add to the deployment. |
| deploymentStrategy | object | `{"type":"RollingUpdate"}` | Deployment strategy configuration. See `kubectl explain deployment.spec.strategy`. |
| 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 |
| dnsPolicy | string | `""` | Define a dnsPolicy. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy |
| extraContainers | list | `[]` | additional sidecar containers to run inside the pod. |
| extraEnvVars | list | `[]` | aditional environment variables passed to the pod |
| extraInitContainers | list | `[]` | additional init containers to run inside the pod. |
| extraPodAnnotations | object | `{}` | additional annotations applied to the pod |
| extraPodLabels | object | `{}` | additional pod labels. Not used as a selector label. |
| extraVolumeMounts | list | `[]` | Define mount points for additional volumes. |
| extraVolumes | list | `[]` | Define additional volumes to mount to the pod. |
| fullnameOverride | string | `""` | |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.repository | string | `"docker.io/jellyfin/jellyfin"` | Set cutom repository for jellyfin image |
| image.tag | string | `""` | Set jellyfin version which should be required since chart is updated with new versions automatically |
| imagePullSecrets | list | `[]` | |
| ingress.annotations | object | `{}` | |
| ingress.enabled | bool | `false` | |
| ingress.hosts[0] | string | `"chart-example.local"` | |
| ingress.labels | object | `{}` | |
| ingress.path | string | `"/"` | |
| ingress.tls | list | `[]` | |
| livenessProbe | object | `{"enabled":true,"initialDelaySeconds":10}` | Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds. |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | |
| 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 |
| fullnameOverride | string | `""` | Override the default full name of the chart. |
| 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/ |
| httpRoute.hostnames | list | `[]` | Hostnames to match for this HTTPRoute |
| httpRoute.parentRefs | list | `[]` | Gateway references to attach this HTTPRoute to |
| httpRoute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Rules for routing traffic |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy (Always, IfNotPresent, or Never). |
| image.repository | string | `"docker.io/jellyfin/jellyfin"` | Container image repository for Jellyfin. |
| image.tag | string | `""` | Jellyfin container image tag. Leave empty to automatically use the Chart's app version. |
| imagePullSecrets | list | `[]` | Image pull secrets to authenticate with private repositories. See: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ |
| 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/ |
| initContainers | list | `[]` | DEPRECATED: Use extraInitContainers instead. Will be removed after 2030. @deprecated - This parameter is deprecated, use extraInitContainers instead |
| jellyfin.args | list | `[]` | Additional arguments for the entrypoint command. |
| jellyfin.command | list | `[]` | Custom command to use as container entrypoint. |
| 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.annotations | object | `{}` | Custom annotations to be added to the PVC |
| persistence.config.enabled | bool | `false` | |
| persistence.config.labels | object | `{}` | Custom labels to be added to the PVC |
| persistence.config.size | string | `"1Gi"` | |
| persistence.config.storageClass | string | `"-"` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
| persistence.extraExistingClaimMounts | list | `[]` | Add aditional PVs/PVCs to our container. Check values.yaml to see an example. |
| persistence.media.accessMode | string | `"ReadWriteOnce"` | |
| persistence.config.enabled | bool | `true` | set to false to use emptyDir |
| persistence.config.size | string | `"5Gi"` | |
| 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"` | PVC specific settings, only used if type is 'pvc'. |
| persistence.media.annotations | object | `{}` | Custom annotations to be added to the PVC |
| persistence.media.enabled | bool | `false` | |
| persistence.media.labels | object | `{}` | Custom labels to be added to the PVC |
| persistence.media.size | string | `"10Gi"` | |
| persistence.media.storageClass | string | `"-"` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
| podSecurityContext | object | `{}` | SecurityContext configuration for the Jellyfin pod |
| readinessProbe | object | `{"enabled":true,"initialDelaySeconds":10}` | Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds. |
| replicaCount | int | `1` | Number of jellyfin replicas to start. Should be left at 1 |
| resources | object | `{}` | |
| runtimeClassName | string | `nil` | |
| securityContext | object | `{}` | SecurityContext configuration for the Jellyfin container |
| service.annotations | object | `{}` | |
| service.labels | object | `{}` | |
| service.loadBalancerIP | string | `nil` | |
| service.port | int | `8096` | |
| service.type | string | `"ClusterIP"` | |
| tolerations | list | `[]` | |
| persistence.media.enabled | bool | `true` | set to false to use emptyDir |
| persistence.media.hostPath | string | `""` | Path on the host node for media storage, only used if type is 'hostPath'. |
| persistence.media.size | string | `"25Gi"` | |
| persistence.media.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. |
| 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. |
| podAnnotations | object | `{}` | Annotations to add to the pod. |
| podLabels | object | `{}` | Additional labels to add to the pod. |
| podPrivileges | object | `{"hostIPC":false,"hostNetwork":false,"hostPID":false}` | Privileged pod settings for advanced use cases |
| 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 |
| podPrivileges.hostNetwork | bool | `false` | Enable hostNetwork. Allows pod to use the host's network namespace. |
| 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)
## 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.
@@ -130,3 +207,274 @@ 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/>
+314 -34
View File
@@ -7,49 +7,31 @@
{{ template "chart.homepageLine" . }}
## Steps to Use a Helm Chart
### 1. Add a Helm Repository
Helm repositories contain collections of charts. You can add an existing repository using the following command:
## Quick Start
```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
To install a chart, use the following command:
For production deployments, create a custom `values.yaml` file with your configuration and install using:
```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:
```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" . }}
This chart is maintained by the [Jellyfin Project](https://jellyfin.org).
{{ template "chart.sourcesSection" . }}
@@ -59,6 +41,33 @@ helm install <release-name> <chart-name> -f values.yaml
{{ 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.
@@ -85,3 +94,274 @@ 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/>
+128
View File
@@ -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"
+58
View File
@@ -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"
+19
View File
@@ -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
+76
View File
@@ -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
+81 -13
View File
@@ -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 }}
Jellyfin is available via Ingress at:
{{- 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 }}
{{- 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}")
echo http://$NODE_IP:$NODE_PORT
Get the Jellyfin URL by running:
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 }}
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" . }}'
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 }}
{{- 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}")
Visit http://127.0.0.1:8080 to use your application
kubectl port-forward $POD_NAME 8080:80
Get the Jellyfin URL by running:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jellyfin.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo "Jellyfin URL: http://$SERVICE_IP:{{ .Values.service.port }}"
{{- 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 }}
{{- 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 }}
+53 -16
View File
@@ -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.
*/}}
{{- define "jellyfin.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
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.
*/}}
{{- define "jellyfin.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "jellyfin.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- 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 }}
-32
View File
@@ -1,32 +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 }}
{{- with .Values.persistence.config.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- 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 }}
{{- if .Values.persistence.config.storageClass }}
{{- if (eq "-" .Values.persistence.config.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.config.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}
+144 -94
View File
@@ -2,29 +2,33 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "jellyfin.fullname" . }}
{{- with .Values.deploymentAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
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 }}
{{- include "jellyfin.labels" . | nindent 4 }}
spec:
{{- with .Values.deploymentStrategy }}
strategy:
type: Recreate
{{- toYaml . | trim | nindent 4 }}
{{- end }}
replicas: {{ .Values.replicaCount }}
{{- if not (eq .Values.revisionHistoryLimit nil) }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- include "jellyfin.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.extraPodLabels }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.extraPodAnnotations }}
annotations:
labels:
{{- include "jellyfin.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
@@ -32,108 +36,125 @@ spec:
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.enableDLNA }}
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
{{- end }}
{{- if .Values.podPrivileges.hostIPC }}
hostIPC: true
{{- end }}
{{- if .Values.podPrivileges.hostPID }}
hostPID: true
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{ if .Values.image.command }}
{{- with .Values.jellyfin.command }}
command:
{{- toYaml .Values.image.command | nindent 12 }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{ if .Values.image.args }}
{{- with .Values.jellyfin.args }}
args:
{{- toYaml .Values.image.args | nindent 12 }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{ if .Values.extraEnvVars }}
{{- with .Values.jellyfin.envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.jellyfin.env }}
env:
{{- toYaml .Values.extraEnvVars | nindent 12 }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{ if .Values.securityContext }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
{{- 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:
- name: http
containerPort: 8096
protocol: TCP
{{ if .Values.enableDLNA }}
{{- if .Values.jellyfin.enableDLNA }}
- name: dlna
containerPort: 1900
hostPort: 1900
protocol: UDP
{{- end }}
{{ if .Values.livenessProbe.enabled }}
{{- with .Values.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
{{ if .Values.livenessProbe.initialDelaySeconds }}
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 }}
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{ if .Values.readinessProbe.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
{{- 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
{{- toYaml .Values.readinessProbe | nindent 12 }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- mountPath: /config
name: config
{{- if .Values.persistence.config.subPath }}
subPath: {{ .Values.persistence.config.subPath }}
{{- end }}
- mountPath: /media
name: media
{{- if .Values.persistence.media.subPath }}
subPath: {{ .Values.persistence.media.subPath }}
- mountPath: /cache
name: cache
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- range .Values.persistence.extraExistingClaimMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
readOnly: {{ .readOnly }}
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
{{- end }}
{{- if .Values.extraContainers }}
{{- toYaml .Values.extraContainers | nindent 8 }}
{{- end }}
resources:
{{ toYaml .Values.resources | indent 12 }}
terminationMessagePolicy: FallbackToLogsOnError
securityContext: {{- toYaml .Values.securityContext | nindent 12 }}
{{- if .Values.extraInitContainers }}
{{- if or .Values.initContainers .Values.extraInitContainers }}
# Support both initContainers (deprecated) and extraInitContainers for backward compatibility.
# TODO: Remove initContainers support after 2030
initContainers:
{{- toYaml .Values.extraInitContainers | nindent 10 }}
{{- if .Values.initContainers }}
{{- toYaml .Values.initContainers | nindent 6 }}
{{- end }}
{{- if .Values.extraInitContainers }}
{{- toYaml .Values.extraInitContainers | nindent 6 }}
{{- end }}
{{- end }}
volumes:
- name: config
@@ -145,32 +166,61 @@ spec:
{{- end }}
- name: media
{{- if .Values.persistence.media.enabled }}
{{- if eq .Values.persistence.media.type "pvc" }}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.media.existingClaim }}{{ .Values.persistence.media.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-media{{- end }}
{{- else if eq .Values.persistence.media.type "hostPath" }}
hostPath:
path: {{ .Values.persistence.media.hostPath }}
type: Directory
{{- else }}
emptyDir: {}
{{- end }}
{{- range .Values.persistence.extraExistingClaimMounts }}
- name: {{ .name }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else }}
emptyDir: {}
{{- end }}
{{- if .Values.extraVolumes }}
{{- toYaml .Values.extraVolumes | nindent 6 }}
- 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:
{{ toYaml . | indent 8 }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{ toYaml . | indent 8 }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- if .Values.runtimeClassName }}
runtimeClassName: {{.Values.runtimeClassName}}
{{- 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 }}
+33
View File
@@ -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 }}
+18 -25
View File
@@ -1,30 +1,19 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "jellyfin.fullname" . -}}
{{- $servicePort := .Values.service.port -}}
{{- $ingressPath := .Values.ingress.path -}}
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
name: {{ include "jellyfin.fullname" . }}
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 }}
{{- with .Values.ingress.labels }}
{{- include "jellyfin.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if or (.Capabilities.APIVersions.Has "networking.k8s.io/v1/IngressClass") (.Capabilities.APIVersions.Has "networking.k8s.io/v1beta1/IngressClass") }}
{{- if .Values.ingress.ingressClassName }}
ingressClassName: {{ .Values.ingress.ingressClassName }}
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- end }}
{{- if .Values.ingress.tls }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
@@ -33,18 +22,22 @@ spec:
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . | quote }}
- host: {{ .host | quote }}
http:
paths:
- pathType: Prefix
path: {{ $ingressPath }}
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ $fullName }}
name: {{ include "jellyfin.fullname" $ }}
port:
name: http
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
-32
View File
@@ -1,32 +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 }}
{{- with .Values.persistence.media.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- 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 }}
{{- 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 }}
+44 -38
View File
@@ -1,52 +1,58 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "jellyfin.fullname" . }}
name: {{ include "jellyfin.fullname" . }}
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 }}
{{- if .Values.service.labels }}
{{ toYaml .Values.service.labels | indent 4 }}
{{- end }}
{{- with .Values.service.annotations }}
annotations:
{{ toYaml . | indent 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 }}
{{- include "jellyfin.labels" . | nindent 4 }}
{{- with .Values.service.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.service.loadBalancerSourceRanges }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
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:
{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }}
{{- end -}}
{{- else }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- else }}
type: {{ .Values.service.type }}
{{- end }}
{{- if .Values.service.externalIPs }}
{{- end }}
{{- 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:
{{ toYaml .Values.service.externalIPs | indent 4 }}
{{- end }}
{{- if .Values.service.externalTrafficPolicy }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.service.externalTrafficPolicy }}
externalTrafficPolicy: {{ . }}
{{- end }}
ports:
- name: http
- name: {{ .Values.service.name | default "http" }}
port: {{ .Values.service.port }}
protocol: TCP
targetPort: http
{{ if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
nodePort: {{.Values.service.nodePort}}
{{ end }}
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
selector:
app.kubernetes.io/name: {{ include "jellyfin.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- include "jellyfin.selectorLabels" . | nindent 4 }}
@@ -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
+270
View File
@@ -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
+193
View File
@@ -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
+475
View File
@@ -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
+666
View File
@@ -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
+447
View File
@@ -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
+440
View File
@@ -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
+814
View File
@@ -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"]
}
]
}
}
}
]
}
+445 -113
View File
@@ -1,101 +1,165 @@
# -- 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
# -- 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:
# -- Set cutom repository for jellyfin image
# -- Container image repository for 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: ""
# -- Image pull policy (Always, IfNotPresent, or Never).
pullPolicy: IfNotPresent
# -- Override the default name of the chart.
nameOverride: ""
# -- Override the default full name of the chart.
fullnameOverride: ""
# -- 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
enableDLNA: false
# -- Service account configuration. See: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# -- Specifies whether to create a service account.
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:
# # -- Custom name for the service port
# name: http
# -- Service type (ClusterIP, NodePort, or LoadBalancer).
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
## Specify the nodePort value for the LoadBalancer and NodePort service types.
## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
##
# 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
##
# -- Name of the port in the service.
portName: service
# -- Annotations for the service.
annotations: {}
# -- Labels for the service.
labels: {}
## Use loadBalancerIP to request a specific static IP,
## otherwise leave blank
##
loadBalancerIP:
# loadBalancerSourceRanges: []
## Set the externalTrafficPolicy in the Service to either Cluster or Local
# -- External traffic policy (Cluster or Local).
# externalTrafficPolicy: Cluster
# -- NodePort for the service (if applicable).
# nodePort:
# -- Ingress configuration. See: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
labels: {}
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.local
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
persistence:
config:
# -- HTTPRoute configuration for Gateway API. See: https://gateway-api.sigs.k8s.io/
httpRoute:
enabled: false
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
storageClass: "-"
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 1Gi
# -- Custom labels to be added to the PVC
labels: {}
# -- Custom annotations to be added to the PVC
annotations: {}
media:
enabled: false
# -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner.
storageClass: "-"
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 10Gi
# -- Custom labels to be added to the PVC
labels: {}
# -- Custom annotations to be added to the PVC
annotations: {}
# -- Gateway references to attach this HTTPRoute to
parentRefs: []
# - name: my-gateway
# namespace: gateway-system
# sectionName: https
# -- Hostnames to match for this HTTPRoute
hostnames: []
# - jellyfin.example.com
# -- Rules for routing traffic
rules:
- matches:
- path:
type: PathPrefix
value: /
# -- 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: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
@@ -103,72 +167,340 @@ resources: {}
# cpu: 100m
# memory: 128Mi
# Define a custom runtimeClassName for the pod, e.g. "nvidia" if you have the NVIDIA GPU Operator installed
runtimeClassName:
# -- 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: {}
# -- Tolerations for pod scheduling.
tolerations: []
# -- Affinity rules for pod scheduling.
affinity: {}
# -- Define additional volumes to mount to the pod.
extraVolumes: []
#- name: renderD129
# hostPath:
# path: /dev/dri/renderD129
# -- Privileged pod settings for advanced use cases
podPrivileges:
# -- Enable hostIPC namespace. Required for NVIDIA MPS (Multi-Process Service) GPU sharing. See: https://docs.nvidia.com/deploy/mps/index.html
hostIPC: false
# -- 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.
extraVolumeMounts: []
#- mountPath: /dev/dri/renderD129
# name: renderD129
jellyfin:
# -- Enable DLNA. Requires host network. See: https://jellyfin.org/docs/general/networking/dlna.html
enableDLNA: false
# -- 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.
extraPodLabels: {}
persistence:
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
extraEnvVars: []
#- name: MY_ENV_VAR
# value: my-env-var-value
# -- Configuration for metrics collection and monitoring
metrics:
# -- Enable or disable metrics collection
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
extraPodAnnotations: {}
# -- DEPRECATED: Use extraInitContainers instead. Will be removed after 2030.
# @deprecated - This parameter is deprecated, use extraInitContainers instead
initContainers: []
# -- additional init containers to run inside the pod.
# -- 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.
extraContainers: []
# -- SecurityContext configuration for the Jellyfin pod
podSecurityContext: {}
# Jellyfin can be run as an unprivileged user with the following settings:
# runAsUser: 65534
# runAsGroup: 65534
# -- 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:
# -- 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).
enabled: false
# -- SecurityContext configuration for the Jellyfin container
securityContext: {}
# For example, you might need to grant the following capabilities to enable hardware acceleration when mounting a /dev/dri device:
# capabilities:
# add:
# - "SYS_ADMIN"
# drop:
# - "ALL"
# -- Policy types to enforce. Both ingress and egress policies can be enabled.
# See: https://kubernetes.io/docs/concepts/services-networking/network-policies/#policy-types
policyTypes:
- Ingress
- Egress
# -- Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds.
livenessProbe:
enabled: true
# failureThreshold: 3
initialDelaySeconds: 10
# periodSeconds: 10
# successThreshold: 1
# timeoutSeconds: 1
# -- Ingress rules configuration - controls which pods/namespaces can access Jellyfin.
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
# -- Larger libraries may need to increase the readinessProbe and livenessProbe timeouts. Start by increasing the initialDelaySeconds.
readinessProbe:
enabled: true
# failureThreshold: 3
initialDelaySeconds: 10
# periodSeconds: 10
# successThreshold: 1
# timeoutSeconds: 1
# -- 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
+12
View File
@@ -0,0 +1,12 @@
# chart-testing configuration
# https://github.com/helm/chart-testing
remote: origin
target-branch: master
chart-dirs:
- charts
chart-repos: []
helm-extra-args: --timeout 600s
validate-maintainers: false
check-version-increment: true
excluded-charts: []