commit 250bd09fc852866851574dc5cd27821231a27f73 Author: Markus Pesch Date: Wed Jul 23 18:14:01 2025 +0200 Initial Commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b53e68c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false + +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/.gitea/scripts/add-annotations.sh b/.gitea/scripts/add-annotations.sh new file mode 100755 index 0000000..b22cf91 --- /dev/null +++ b/.gitea/scripts/add-annotations.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +set -e + +CHART_FILE="Chart.yaml" +if [ ! -f "${CHART_FILE}" ]; then + echo "ERROR: ${CHART_FILE} not found!" 1>&2 + exit 1 +fi + +DEFAULT_NEW_TAG="$(git tag --sort=-version:refname | head -n 1)" +DEFAULT_OLD_TAG="$(git tag --sort=-version:refname | head -n 2 | tail -n 1)" + +if [ -z "${1}" ]; then + read -p "Enter start tag [${DEFAULT_OLD_TAG}]: " OLD_TAG + if [ -z "${OLD_TAG}" ]; then + OLD_TAG="${DEFAULT_OLD_TAG}" + fi + + while [ -z "$(git tag --list "${OLD_TAG}")" ]; do + echo "ERROR: Tag '${OLD_TAG}' not found!" 1>&2 + read -p "Enter start tag [${DEFAULT_OLD_TAG}]: " OLD_TAG + if [ -z "${OLD_TAG}" ]; then + OLD_TAG="${DEFAULT_OLD_TAG}" + fi + done +else + OLD_TAG=${1} + if [ -z "$(git tag --list "${OLD_TAG}")" ]; then + echo "ERROR: Tag '${OLD_TAG}' not found!" 1>&2 + exit 1 + fi +fi + +if [ -z "${2}" ]; then + read -p "Enter end tag [${DEFAULT_NEW_TAG}]: " NEW_TAG + if [ -z "${NEW_TAG}" ]; then + NEW_TAG="${DEFAULT_NEW_TAG}" + fi + + while [ -z "$(git tag --list "${NEW_TAG}")" ]; do + echo "ERROR: Tag '${NEW_TAG}' not found!" 1>&2 + read -p "Enter end tag [${DEFAULT_NEW_TAG}]: " NEW_TAG + if [ -z "${NEW_TAG}" ]; then + NEW_TAG="${DEFAULT_NEW_TAG}" + fi + done +else + NEW_TAG=${2} + + if [ -z "$(git tag --list "${NEW_TAG}")" ]; then + echo "ERROR: Tag '${NEW_TAG}' not found!" 1>&2 + exit 1 + fi +fi + +CHANGE_LOG_YAML=$(mktemp) +echo "[]" > "${CHANGE_LOG_YAML}" + +function map_type_to_kind() { + case "${1}" in + feat) + echo "added" + ;; + fix) + echo "fixed" + ;; + chore|style|test|ci|docs|refac) + echo "changed" + ;; + revert) + echo "removed" + ;; + sec) + echo "security" + ;; + *) + echo "skip" + ;; + esac +} + +COMMIT_TITLES="$(git log --pretty=format:"%s" "${OLD_TAG}..${NEW_TAG}")" + +echo "INFO: Generate change log entries from ${OLD_TAG} until ${NEW_TAG}" + +while IFS= read -r line; do + if [[ "${line}" =~ ^([a-zA-Z]+)(\([^\)]+\))?\:\ (.+)$ ]]; then + TYPE="${BASH_REMATCH[1]}" + KIND=$(map_type_to_kind "${TYPE}") + + if [ "${KIND}" == "skip" ]; then + continue + fi + + DESC="${BASH_REMATCH[3]}" + + echo "- ${KIND}: ${DESC}" + + jq --arg kind "${KIND}" --arg description "${DESC}" '. += [ $ARGS.named ]' < "${CHANGE_LOG_YAML}" > "${CHANGE_LOG_YAML}.new" + mv "${CHANGE_LOG_YAML}.new" "${CHANGE_LOG_YAML}" + + fi +done <<< "${COMMIT_TITLES}" + +if [ -s "${CHANGE_LOG_YAML}" ]; then + yq --inplace --input-format json --output-format yml "${CHANGE_LOG_YAML}" + yq --no-colors --inplace ".annotations.\"artifacthub.io/changes\" |= loadstr(\"${CHANGE_LOG_YAML}\") | sort_keys(.)" "${CHART_FILE}" +else + echo "ERROR: Changelog file is empty: ${CHANGE_LOG_YAML}" 1>&2 + exit 1 +fi + +rm "${CHANGE_LOG_YAML}" diff --git a/.gitea/workflows/generate-readme.yaml b/.gitea/workflows/generate-readme.yaml new file mode 100644 index 0000000..6708ab1 --- /dev/null +++ b/.gitea/workflows/generate-readme.yaml @@ -0,0 +1,32 @@ +name: Generate README + +on: + pull_request: + paths: [ "README.md", "values.yaml" ] + types: [ "opened", "reopened", "synchronize" ] + push: + branches: + - '**' + paths: [ "README.md", "values.yaml" ] + tags-ignore: + - '**' + workflow_dispatch: {} + +jobs: + generate-parameters: + container: + image: docker.io/library/node:24.1.0-alpine + runs-on: + - ubuntu-latest + steps: + - name: Install tooling + run: | + apk update + apk add git npm + - uses: actions/checkout@v4.2.2 + - name: Generate parameter section in README + run: | + npm install + npm run readme:parameters + - name: Compare diff + run: git diff --exit-code --name-only README.md diff --git a/.gitea/workflows/helm.yaml b/.gitea/workflows/helm.yaml new file mode 100644 index 0000000..a293898 --- /dev/null +++ b/.gitea/workflows/helm.yaml @@ -0,0 +1,42 @@ +name: Helm + +on: + pull_request: + types: [ "opened", "reopened", "synchronize" ] + push: + branches: + - '**' + tags-ignore: + - '**' + workflow_dispatch: {} + +jobs: + helm-lint: + container: + image: docker.io/volkerraschek/helm:3.18.2 + runs-on: + - ubuntu-latest + steps: + - name: Install tooling + run: | + apk update + apk add git npm + - uses: actions/checkout@v4.2.2 + - name: Lint helm files + run: | + helm lint --values values.yaml . + + helm-unittest: + container: + image: docker.io/volkerraschek/helm:3.18.2 + runs-on: + - ubuntu-latest + steps: + - name: Install tooling + run: | + apk update + apk add git npm + - uses: actions/checkout@v4.2.2 + - name: Unittest + run: | + helm unittest --strict --file 'unittests/**/*.yaml' ./ \ No newline at end of file diff --git a/.gitea/workflows/markdown-linters.yaml b/.gitea/workflows/markdown-linters.yaml new file mode 100644 index 0000000..4b27fd2 --- /dev/null +++ b/.gitea/workflows/markdown-linters.yaml @@ -0,0 +1,46 @@ +name: Markdown linter + +on: + pull_request: + paths: [ "**/*.md" ] + types: [ "opened", "reopened", "synchronize" ] + push: + branches: + - '**' + paths: [ "**/*.md" ] + tags-ignore: + - '**' + workflow_dispatch: {} + +jobs: + markdown-link-checker: + container: + image: docker.io/library/node:24.1.0-alpine + runs-on: + - ubuntu-latest + steps: + - name: Install tooling + run: | + apk update + apk add git npm + - uses: actions/checkout@v4.2.2 + - name: Verify links in markdown files + run: | + npm install + npm run readme:link + + markdown-lint: + container: + image: docker.io/library/node:24.1.0-alpine + runs-on: + - ubuntu-latest + steps: + - name: Install tooling + run: | + apk update + apk add git + - uses: actions/checkout@v4.2.2 + - name: Lint markdown files + run: | + npm install + npm run readme:lint diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..ebe41f9 --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + tags: + - "**" + +jobs: + publish-chart: + container: + image: docker.io/volkerraschek/helm:3.18.2 + runs-on: ubuntu-latest + steps: + - name: Install packages via apk + run: | + apk update + apk add git npm jq yq + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Add Artifacthub.io annotations + run: | + NEW_TAG="$(git tag --sort=-version:refname | head -n 1)" + OLD_TAG="$(git tag --sort=-version:refname | head -n 2 | tail -n 1)" + .gitea/scripts/add-annotations.sh "${OLD_TAG}" "${NEW_TAG}" + + - name: Extract meta information + run: | + echo "PACKAGE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + echo "REPOSITORY_NAME=$(echo ${GITHUB_REPOSITORY} | cut -d '/' -f 2 | sed --regexp-extended 's/-charts?//g')" >> $GITHUB_ENV + echo "REPOSITORY_OWNER=$(echo ${GITHUB_REPOSITORY} | cut -d '/' -f 1)" >> $GITHUB_ENV + + - name: Update Helm Chart version in README.md + run: sed -i -E "s/^CHART_VERSION=.*/CHART_VERSION=${PACKAGE_VERSION}/g" README.md + + - name: Package chart + run: | + helm dependency build + helm package --version "${PACKAGE_VERSION}" ./ + + - name: Upload Chart to ChartMuseum + env: + CHARTMUSEUM_PASSWORD: ${{ secrets.CHARTMUSEUM_PASSWORD }} + CHARTMUSEUM_REPOSITORY: ${{ vars.CHARTMUSEUM_REPOSITORY }} + CHARTMUSEUM_USERNAME: ${{ secrets.CHARTMUSEUM_USERNAME }} + CHARTMUSEUM_HOSTNAME: ${{ vars.CHARTMUSEUM_HOSTNAME }} + run: | + helm repo add --username ${CHARTMUSEUM_USERNAME} --password ${CHARTMUSEUM_PASSWORD} chartmuseum https://${CHARTMUSEUM_HOSTNAME}/${CHARTMUSEUM_REPOSITORY} + helm cm-push ${REPOSITORY_NAME}-${PACKAGE_VERSION}.tgz chartmuseum + helm repo remove chartmuseum + + - name: Upload Chart to Gitea + env: + GITEA_PACKAGE_REGISTRY_TOKEN: ${{ secrets.GIT_CRYPTIC_SYSTEMS_PACKAGE_REGISTRY_TOKEN }} + GITEA_SERVER_URL: ${{ github.server_url }} + run: | + helm repo add --username ${REPOSITORY_OWNER} --password ${GITEA_PACKAGE_REGISTRY_TOKEN} gitea ${GITEA_SERVER_URL}/api/packages/${REPOSITORY_OWNER}/helm + helm cm-push ${REPOSITORY_NAME}-${PACKAGE_VERSION}.tgz gitea + helm repo remove gitea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba69fff --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +charts +node_modules +target +values2.yml +values2.yaml +*.tgz diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..a954747 --- /dev/null +++ b/.helmignore @@ -0,0 +1,60 @@ +# 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/ + +# drone +.drone.yml + +# editorconfig +.editorconfig + +# customized values +values2.yml +values2.yaml + +# helm packages +*.tgz +.helmignore +unittests + +# markdownlint +.markdownlint.yml +.markdownlint.yaml +.markdownlintignore + +# npm +.prettierignore +.npmrc +package* + +# yamllint +.yamllint.yaml + +# Others +CONTRIBUTING.md +CODEOWNERS +Makefile +renovate.json diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..5156987 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,156 @@ +# markdownlint YAML configuration +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# Path to configuration file to extend +extends: null + +# MD003/heading-style/header-style - Heading style +MD003: + # Heading style + style: "atx" + +# MD004/ul-style - Unordered list style +MD004: + style: "dash" + +# MD007/ul-indent - Unordered list indentation +MD007: + # Spaces for indent + indent: 2 + # Whether to indent the first level of the list + start_indented: false + +# MD009/no-trailing-spaces - Trailing spaces +MD009: + # Spaces for line break + br_spaces: 2 + # Allow spaces for empty lines in list items + list_item_empty_lines: false + # Include unnecessary breaks + strict: false + +# MD010/no-hard-tabs - Hard tabs +MD010: + # Include code blocks + code_blocks: true + +# MD012/no-multiple-blanks - Multiple consecutive blank lines +MD012: + # Consecutive blank lines + maximum: 1 + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 120 + # Number of characters for headings + heading_line_length: 120 + # Number of characters for code blocks + code_block_line_length: 80 + # Include code blocks + code_blocks: false + # Include tables + tables: false + # Include headings + headings: true + # Strict length checking + strict: false + # Stern length checking + stern: false + +# MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines +MD022: + # Blank lines above heading + lines_above: 1 + # Blank lines below heading + lines_below: 1 + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: true + +# MD025/single-title/single-h1 - Multiple top-level headings in the same document +MD025: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD026/no-trailing-punctuation - Trailing punctuation in heading +MD026: + # Punctuation characters + punctuation: ".,;:!。,;:!" + +# MD029/ol-prefix - Ordered list item prefix +MD029: + # List style + style: "one_or_ordered" + +# MD030/list-marker-space - Spaces after list markers +MD030: + # Spaces for single-line unordered list items + ul_single: 1 + # Spaces for single-line ordered list items + ol_single: 1 + # Spaces for multi-line unordered list items + ul_multi: 1 + # Spaces for multi-line ordered list items + ol_multi: 1 + +# MD033/no-inline-html - Inline HTML +MD033: + # Allowed elements + allowed_elements: [] + +# MD035/hr-style - Horizontal rule style +MD035: + # Horizontal rule style + style: "---" + +# MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading +MD036: + # Punctuation characters + punctuation: ".,;:!?。,;:!?" + +# MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading +MD041: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD044/proper-names - Proper names should have the correct capitalization +MD044: + # List of proper names + names: + - Git + - GitDevOps + - Gitea + - GitHub + - GitLab + - GitOps + - kube-prometheus-stack + - Memcached + - Oracle + - ORBIS U + - PostgreSQL + - Prometheus + - prometheus-exporter + - SSL + - TLS + # Include code blocks + code_blocks: false + +# MD046/code-block-style - Code block style +MD046: + # Block style + style: "fenced" + +# MD048/code-fence-style - Code fence style +MD048: + # Code fence syle + style: "backtick" diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..9fe4803 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,4 @@ +.github/ +Chart.lock +charts/ +node_modules/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fafeafa --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +Chart.lock \ No newline at end of file diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..90128be --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,20 @@ +--- +extends: default + +ignore: | + .yamllint + node_modules + templates + + +rules: + truthy: + allowed-values: ['true', 'false'] + check-keys: False + level: error + line-length: disable + document-start: disable + comments: + min-spaces-from-content: 1 + braces: + max-spaces-inside: 2 \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..af45fc8 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @volker.raschek diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6347fe2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing + +I am very happy if you would like to provide a pull request 👍 + +The content of this file describes which requirements contributors should fulfill before submitting a pull request (PR). + +1. [Valid Git commits](#valid-git-commits) + +## Valid Git commits + +### Commit message + +The repository is subject to a strict commit message template. This states that there are several types of commits. For +example, `fix`, `chore`, `refac`, `test` or `doc`. All types are described in more detail below. + +| type | description | +| ------------------- | ----------------------------------------------------------------- | +| `feat` | New feature. | +| `fix` | Fixes a bug. | +| `refac` | Refactoring production code. | +| `style` | Fixes formatting issues. No production code change. | +| `docs` | Adapt documentation. No production code change. | +| `test` | Adds new or modifies existing tests. No production code change. | +| `chore` | Updating grunt tasks. Is everything which the user does not see. | + +Based on these types, commit messaged can then be created. Here are a few examples: + +```text +style(README): Wrong indentation +feat(deployment): support restartPolicy +fix(my-app): Add missing volume +docs(CONTRIBUTING): Describe how to commit correctly +``` + +This type of commit message makes it easier for me as maintainer to keep an overview and does not cause the commits of a +pull request PR to be combined into one commit (squashing). + +### Smart commits + +Smart commits are excellent when it comes to tracking bugs or issues. In this repository, however, the rebasing of +commits is prohibited, which means that only merge commits are possible. This means that a smart commit message only +needs to be added to the merge commit. + +This has the advantage that the maintainer can use the smart commit to find the merge commit and undo the entire history +of a merge without having to select individual commits. The following history illustrates the correct use of smart commits. + +```text +* 823edbc7 Volker Raschek (G) | [Close #2] feat(deployment): support additional containers +|\ +| * 321aebc3 Volker Raschek (G) | doc(README): generate README with new deployment attributes +| * 8d101dd3 Volker Raschek (G) | test(deployment): Extend unittest of additional containers +| * 6f2abd93 Volker Raschek (G) | fix(deployment): Extend deployment of additional containers +|/ +* aa5ebda bob (N) | [Close #1] feat(deployment): support initContainers +``` + +### Commit signing + +Another problem with Git is the chain of trust. Git allows the configuration of any name and e-mail address. An attacker +can impersonate any person and submit pull requests under a false identity. For as Linux Torvalds, the maintainer of the +Linux kernel. + +```bash +git config --global user.name 'Linux Torvalds' +git config --global user.email 'torvalds@linux-foundation.org' +``` + +To avoid this, some Git repositories expect signed commits. In particular, repositories that are subject to direct +delivery to customers. For this reason, the repository is subject to a branch protection rule that only allows signed +commits. *Until* there is *no verified* and *no signed* commit, the pull request is blocked. + +The following articles describes how Git can be configured to sign commits. Please keep in mind, that the e-mail +address, which is used as UID of the GPG keyring must also be defined in the profile settings of your GitHub account. +Otherwise will be marked the Git commit as *Unverified*. + +1. [Signing Commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) +2. [Tell Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + +Inspect your Git commit via `git log`. There should be mentioned, that your commit is signed. + +Furthermore, the GPG key is unique. **Don't loose your private GPG key**. Backup your private key on a safe device. For +example an external USB drive. diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..2a2b099 --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,26 @@ +annotations: + artifacthub.io/links: | + - name: Reposilite (binary) + url: https://github.com/dzikoysk/reposilite + - name: support + url: https://git.cryptic.systems/volker.raschek/reposilite-charts/issues +apiVersion: v2 +appVersion: "3.5.25" +description: | + Lightweight and easy-to-use repository management software + dedicated for the Maven based artifacts in the JVM ecosystem +icon: https://raw.githubusercontent.com/dzikoysk/reposilite/main/reposilite-site/public/images/favicon.png +keywords: + - artifact-repository-manager + - deploy + - gradle + - maven + - reposilite + - repository-manager + - sbt +name: reposilite +sources: + - https://github.com/dzikoysk/reposilite + - https://git.cryptic.systems/volker.raschek/reposilite-charts +type: application +version: "0.1.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0d79dbb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Markus Pesch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..323bccd --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +# CONTAINER_RUNTIME +CONTAINER_RUNTIME?=$(shell which podman) + +# HELM_IMAGE +HELM_IMAGE_REGISTRY_HOST?=docker.io +HELM_IMAGE_REPOSITORY?=volkerraschek/helm +HELM_IMAGE_VERSION?=3.18.2 # renovate: datasource=docker registryUrl=https://registry-nexus.orbis.dedalus.com depName=volkerraschek/helm +HELM_IMAGE_FULLY_QUALIFIED=${HELM_IMAGE_REGISTRY_HOST}/${HELM_IMAGE_REPOSITORY}:${HELM_IMAGE_VERSION} + +# NODE_IMAGE +NODE_IMAGE_REGISTRY_HOST?=docker.io +NODE_IMAGE_REPOSITORY?=library/node +NODE_IMAGE_VERSION?=24.1.0-alpine # renovate: datasource=docker registryUrl=https://docker.io depName=docker.io/library/node packageName=library/node +NODE_IMAGE_FULLY_QUALIFIED=${NODE_IMAGE_REGISTRY_HOST}/${NODE_IMAGE_REPOSITORY}:${NODE_IMAGE_VERSION} + +# MISSING DOT +# ============================================================================== +missing-dot: + grep --perl-regexp '## @(param|skip).*[^.]$$' values.yaml + +# CONTAINER RUN - README +# ============================================================================== +PHONY+=container-run/readme +container-run/readme: container-run/readme/link container-run/readme/lint container-run/readme/parameters + +container-run/readme/link: + ${CONTAINER_RUNTIME} run \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${NODE_IMAGE_FULLY_QUALIFIED} \ + npm install && npm run readme:link + +container-run/readme/lint: + ${CONTAINER_RUNTIME} run \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${NODE_IMAGE_FULLY_QUALIFIED} \ + npm install && npm run readme:lint + +container-run/readme/parameters: + ${CONTAINER_RUNTIME} run \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${NODE_IMAGE_FULLY_QUALIFIED} \ + npm install && npm run readme:parameters + +# CONTAINER RUN - HELM UNITTESTS +# ============================================================================== +PHONY+=container-run/helm-unittests +container-run/helm-unittests: + ${CONTAINER_RUNTIME} run \ + --env HELM_REPO_PASSWORD=${CHART_SERVER_PASSWORD} \ + --env HELM_REPO_USERNAME=${CHART_SERVER_USERNAME} \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${HELM_IMAGE_FULLY_QUALIFIED} \ + unittest --strict --file 'unittests/**/*.yaml' ./ + +# CONTAINER RUN - HELM UPDATE DEPENDENCIES +# ============================================================================== +PHONY+=container-run/helm-update-dependencies +container-run/helm-update-dependencies: + ${CONTAINER_RUNTIME} run \ + --env HELM_REPO_PASSWORD=${CHART_SERVER_PASSWORD} \ + --env HELM_REPO_USERNAME=${CHART_SERVER_USERNAME} \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${HELM_IMAGE_FULLY_QUALIFIED} \ + dependency update + +# CONTAINER RUN - MARKDOWN-LINT +# ============================================================================== +PHONY+=container-run/helm-lint +container-run/helm-lint: + ${CONTAINER_RUNTIME} run \ + --rm \ + --volume $(shell pwd):$(shell pwd) \ + --workdir $(shell pwd) \ + ${HELM_IMAGE_FULLY_QUALIFIED} \ + lint --values values.yaml . + +# PHONY +# ============================================================================== +# Declare the contents of the PHONY variable as phony. We keep that information +# in a variable so we can use it in if_changed. +.PHONY: ${PHONY} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd7ad7b --- /dev/null +++ b/README.md @@ -0,0 +1,293 @@ +# Reposilite + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/reposilite)](https://artifacthub.io/packages/search?repo=reposilite) + +This helm chart enables the deployment of [Reposilite](https://github.com/dzikoysk/reposilite), a lightweight and +easy-to-use repository management software dedicated for the Maven-based artifacts in the JVM ecosystem. + +The helm chart supports the individual configuration of additional containers/initContainers, mounting of volumes, +defining additional environment variables and much more. + +Chapter [configuration and installation](#helm-configuration-and-installation) describes the basics how to configure helm +and use it to deploy the exporter. It also contains further configuration examples. + +Furthermore, this helm chart contains unit tests to detect regressions and stabilize the deployment. Additionally, this +helm chart is tested for deployment scenarios with **ArgoCD**. + +## Helm: configuration and installation + +1. A helm chart repository must be configured, to pull the helm charts from. +2. All available [parameters](#parameters) are documented in detail below. The parameters can be defined via the helm + `--set` flag or directly as part of a `values.yaml` file. The following example defines the `reposilite` repository + and use the `--set` flag for a basic deployment. + +```bash +helm repo add reposilite https://helm.reposilite.com/ +helm repo update +helm install reposilite reposilite/reposilite +``` + +Instead of passing all parameters via the *set* flag, it is also possible to define them as part of the `values.yaml`. +The following command downloads the `values.yaml` for a specific version of this chart. Please keep in mind, that the +version of the chart must be in sync with the `values.yaml`. Newer *minor* versions can have new features. New *major* +versions can break something! + +```bash +CHART_VERSION=2.0.0 +helm show values reposilite/reposilite --version "${CHART_VERSION}" > values.yaml +``` + +A complete list of available helm chart versions can be displayed via the following command: + +```bash +helm search repo reposilite --versions +``` + +The helm chart also contains a persistent volume claim definition. It persistent volume claim is not enabled by default. +Use the `--set` argument to persist your data. + +```bash +CHART_VERSION=2.0.0 +helm install --version "${CHART_VERSION}" reposilite reposilite/reposilite \ + persistentVolumeClaim.enabled=true +``` + +### Examples + +The following examples serve as individual configurations and as inspiration for how deployment problems can be solved. + +#### TLS encryption + +The example describe how to deploy Reposilite with TLS encryption. If Reposilite is deployed behind reverse proxy, for +example an ingress nginx controller, please instruct the ingress to establish an TLS encrypted connection to avoid +connection problems. + +> [!WARNING] +> The secret `reposilite-tls` containing the TLS certificate is already present. The keys `ca.crt`, `tls.key` and +> `tls.crt` must be renamed matching the file extension `.pem`. Otherwise throws the used TLS library of Reposilite and +> error. + +```bash +CHART_VERSION=2.0.0 +helm install --version "${CHART_VERSION}" reposilite reposilite/reposilite \ + --set 'deployment.reposilite.env[1].name=REPOSILITE_LOCAL_SSLENABLED' \ + --set 'deployment.reposilite.env[1].value="true"' \ + --set 'deployment.reposilite.env[2].name=REPOSILITE_LOCAL_SSLPORT' \ + --set 'deployment.reposilite.env[2].value=8443' \ + --set 'deployment.reposilite.env[3].name=REPOSILITE_LOCAL_KEYPATH' \ + --set 'deployment.reposilite.env[3].value="/etc/reposilite/tls/cert.pem /etc/reposilite/tls/priv-key.pem"' \ + --set 'deployment.reposilite.volumeMounts[0].name=tls' \ + --set 'deployment.reposilite.volumeMounts[0].mountPath=/etc/reposilite/tls' \ + --set 'deployment.reposilite.volumeMounts[0].readOnly=true' \ + --set 'deployment.volumes[0].name=tls' \ + --set 'deployment.volumes[0].items[0].key=ca.crt' \ + --set 'deployment.volumes[0].items[0].path=ca.pem' \ + --set 'deployment.volumes[0].items[1].key=tls.crt' \ + --set 'deployment.volumes[0].items[1].path=cert.pem' \ + --set 'deployment.volumes[0].items[2].key=tls.key' \ + --set 'deployment.volumes[0].items[2].path=priv-key.pem' \ + --set 'deployment.volumes[0].secret.secretName=reposilite-tls' \ + --set 'service.port=8443' +``` + +#### TLS certificate rotation + +If Reposilite uses TLS certificates that are mounted as a secret in the container file system like the example +[above](#tls-encryption), Reposlite will not automatically apply them when the TLS certificates are rotated. Such a +rotation can be for example triggered, when the [cert-manager](https://cert-manager.io/) issues new TLS certificates +before expiring. + +Until Reposilite does not support rotating TLS certificate a workaround can be applied. For example stakater's +[reloader](https://github.com/stakater/Reloader) controller can be used to trigger a rolling update. The following +annotation must be added to instruct the reloader controller to trigger a rolling update, when the mounted configMaps +and secrets have been changed. + +```yaml +deployment: + annotations: + reloader.stakater.com/auto: "true" +``` + +Instead of triggering a rolling update for configMap and secret resources, this action can also be defined for +individual items. For example, when the secret named `reposilite-tls` is mounted and the reloader controller should only +listen for changes of this secret: + +```yaml +deployment: + annotations: + secret.reloader.stakater.com/reload: "reposilite-tls" +``` + +### Network policies + +Network policies can only take effect, when the used CNI plugin support network policies. The chart supports no custom +network policy implementation of CNI plugins. It's support only the official API resource of `networking.k8s.io/v1`. + +The example below is an excerpt of the `values.yaml` file. The network policy contains ingress rules to allow incoming +traffic from an ingress controller. Additionally one egress rule is defined, to allow the application outgoing access +to the internal running DNS server `core-dns`. + +> [!IMPORTANT] +> Please keep in mind, that the namespace and pod selector labels can be different from environment to environment. For +> this reason, there is are not default network policy rules defined. + +```yaml +networkPolicies: + enabled: true + annotations: {} + labels: {} + policyTypes: + - Egress + - Ingress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: TCP + - port: 53 + protocol: UDP + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + ports: + - port: http + protocol: TCP +``` + +## Parameters + +### Global + +| Name | Description | Value | +| ------------------ | ----------------------------------------- | ----- | +| `nameOverride` | Individual release name suffix. | `""` | +| `fullnameOverride` | Override the complete release name logic. | `""` | + +### Deployment + +| Name | Description | Value | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------- | +| `deployment.annotations` | Additional deployment annotations. | `{}` | +| `deployment.labels` | Additional deployment labels. | `{}` | +| `deployment.additionalContainers` | List of additional containers. | `[]` | +| `deployment.affinity` | Affinity for the Reposilite deployment. | `{}` | +| `deployment.initContainers` | List of additional init containers. | `[]` | +| `deployment.dnsConfig` | dnsConfig of the Reposilite deployment. | `{}` | +| `deployment.dnsPolicy` | dnsPolicy of the Reposilite deployment. | `""` | +| `deployment.hostname` | Individual hostname of the pod. | `""` | +| `deployment.subdomain` | Individual domain of the pod. | `""` | +| `deployment.hostNetwork` | Use the kernel network namespace of the host system. | `false` | +| `deployment.imagePullSecrets` | Secret to use for pulling the image. | `[]` | +| `deployment.reposilite.args` | Arguments passed to the Reposilite container. | `[]` | +| `deployment.reposilite.command` | Command passed to the Reposilite container. | `[]` | +| `deployment.reposilite.env` | List of environment variables for the Reposilite container. | | +| `deployment.reposilite.envFrom` | List of environment variables mounted from configMaps or secrets for the Reposilite container. | `[]` | +| `deployment.reposilite.image.registry` | Image registry, eg. `docker.io`. | `docker.io` | +| `deployment.reposilite.image.repository` | Image repository, eg. `library/busybox`. | `dzikoysk/reposilite` | +| `deployment.reposilite.image.tag` | Custom image tag, eg. `0.1.0`. Defaults to `appVersion`. | `""` | +| `deployment.reposilite.image.pullPolicy` | Image pull policy. | `IfNotPresent` | +| `deployment.reposilite.resources` | CPU and memory resources of the pod. | `{}` | +| `deployment.reposilite.securityContext` | Security context of the container of the deployment. | `{}` | +| `deployment.reposilite.volumeMounts` | Additional volume mounts. | `[]` | +| `deployment.nodeSelector` | NodeSelector of the Reposilite deployment. | `{}` | +| `deployment.priorityClassName` | PriorityClassName of the Reposilite deployment. | `""` | +| `deployment.replicas` | Number of replicas for the Reposilite deployment. | `1` | +| `deployment.restartPolicy` | Restart policy of the Reposilite deployment. | `""` | +| `deployment.securityContext` | Security context of the Reposilite deployment. | `{}` | +| `deployment.strategy.type` | Strategy type - `Recreate` or `RollingUpdate`. | `RollingUpdate` | +| `deployment.strategy.rollingUpdate.maxSurge` | The maximum number of pods that can be scheduled above the desired number of pods during a rolling update. | `1` | +| `deployment.strategy.rollingUpdate.maxUnavailable` | The maximum number of pods that can be unavailable during a rolling update. | `1` | +| `deployment.terminationGracePeriodSeconds` | How long to wait until forcefully kill the pod. | `60` | +| `deployment.tolerations` | Tolerations of the Reposilite deployment. | `[]` | +| `deployment.topologySpreadConstraints` | TopologySpreadConstraints of the Reposilite deployment. | `[]` | +| `deployment.volumes` | Additional volumes to mount into the pods of the prometheus-exporter deployment. | `[]` | + +### Horizontal Pod Autoscaler (HPA) + +| Name | Description | Value | +| ------------------------ | -------------------------------------------------------------------------------------------------- | ----------- | +| `hpa.enabled` | Enable the horizontal pod autoscaler (HPA). | `false` | +| `hpa.annotations` | Additional annotations for the HPA. | `{}` | +| `hpa.labels` | Additional labels for the HPA. | `{}` | +| `hpa.behavior.scaleDown` | Scaling policy for scaling down. | | +| `hpa.metrics` | Metrics contains the specifications for which to use to calculate the desired replica count. | `undefined` | +| `hpa.minReplicas` | Min replicas is the lower limit for the number of replicas to which the autoscaler can scale down. | `1` | +| `hpa.maxReplicas` | Upper limit for the number of pods that can be set by the autoscaler. | `10` | + +### Ingress + +| Name | Description | Value | +| --------------------- | ----------------------------------------------------------------------------------------------- | ------- | +| `ingress.enabled` | Enable creation of an ingress resource. Requires, that the http service is also enabled. | `false` | +| `ingress.className` | Ingress class. | `nginx` | +| `ingress.annotations` | Additional ingress annotations. | `{}` | +| `ingress.labels` | Additional ingress labels. | `{}` | +| `ingress.hosts` | Ingress specific configuration. Let ingress controller listen on specific DNS names and path's. | `[]` | +| `ingress.tls` | Ingress specific TLS configuration. Use TLS encryption for specific hostnames. | `[]` | + +### Network Policy + +| Name | Description | Value | +| --------------------------- | ------------------------------------------------------------------------- | ------- | +| `networkPolicy.enabled` | Enable network policies in general. | `false` | +| `networkPolicy.annotations` | Additional network policy annotations. | `{}` | +| `networkPolicy.labels` | Additional network policy labels. | `{}` | +| `networkPolicy.policyTypes` | List of policy types. Supported is ingress, egress or ingress and egress. | `[]` | +| `networkPolicy.egress` | Concrete egress network policy implementation. | `[]` | +| `networkPolicy.ingress` | Concrete ingress network policy implementation. | `[]` | + +### Persistent Volume Claim + +| Name | Description | Value | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `persistentVolumeClaim.enabled` | Enable persistence in general. | `false` | +| `persistentVolumeClaim.path` | Path where the persistent volume claim should be mounted into the container file system. | `/app/data` | +| `persistentVolumeClaim.existing.enabled` | Enable an existing persistent volume claim. | `false` | +| `persistentVolumeClaim.existing.persistentVolumeClaimName` | Name of the existing persistent volume claim. | `""` | +| `persistentVolumeClaim.new.annotations` | Additional service account annotations. | `{}` | +| `persistentVolumeClaim.new.labels` | Additional service account labels. | `{}` | +| `persistentVolumeClaim.new.accessMode` | Access mode of the persistent volume claim. More information about the access modes are in detail documented [online](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes). | `ReadWriteOnce` | +| `persistentVolumeClaim.new.size` | Size of the persistent volume claim. | `10Gi` | +| `persistentVolumeClaim.new.storageClass` | Custom storage class. Left it empty to use the clusters default storage class. | `""` | + +### Service + +| Name | Description | Value | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `service.enabled` | Enable the service. | `true` | +| `service.annotations` | Additional service annotations. | `{}` | +| `service.externalIPs` | External IPs for the service. | `[]` | +| `service.externalTrafficPolicy` | If `service.type` is `NodePort` or `LoadBalancer`, set this to `Local` to tell kube-proxy to only use node local endpoints for cluster external traffic. Furthermore, this enables source IP preservation. | `Cluster` | +| `service.internalTrafficPolicy` | If `service.type` is `NodePort` or `LoadBalancer`, set this to `Local` to tell kube-proxy to only use node local endpoints for cluster internal traffic. | `Cluster` | +| `service.ipFamilies` | IPFamilies is list of IP families (e.g. `IPv4`, `IPv6`) assigned to this service. This field is usually assigned automatically based on cluster configuration and only required for customization. | `[]` | +| `service.labels` | Additional service labels. | `{}` | +| `service.loadBalancerClass` | LoadBalancerClass is the class of the load balancer implementation this Service belongs to. Requires service from type `LoadBalancer`. | `""` | +| `service.loadBalancerIP` | LoadBalancer will get created with the IP specified in this field. Requires service from type `LoadBalancer`. | `""` | +| `service.loadBalancerSourceRanges` | Source range filter for LoadBalancer. Requires service from type `LoadBalancer`. | `[]` | +| `service.port` | Port to forward the traffic to. | `8080` | +| `service.sessionAffinity` | Supports `ClientIP` and `None`. Enable client IP based session affinity via `ClientIP`. | `None` | +| `service.sessionAffinityConfig` | Contains the configuration of the session affinity. | `{}` | +| `service.type` | Kubernetes service type for the traffic. | `ClusterIP` | + +### ServiceAccount + +| Name | Description | Value | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `serviceAccount.existing.enabled` | Use an existing service account instead of creating a new one. Assumes that the user has all the necessary kubernetes API authorizations. | `false` | +| `serviceAccount.existing.serviceAccountName` | Name of the existing service account. | `""` | +| `serviceAccount.new.annotations` | Additional service account annotations. | `{}` | +| `serviceAccount.new.labels` | Additional service account labels. | `{}` | +| `serviceAccount.new.automountServiceAccountToken` | Enable/disable auto mounting of the service account token. | `true` | +| `serviceAccount.new.imagePullSecrets` | ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images in pods that reference this serviceAccount. | `[]` | +| `serviceAccount.new.secrets` | Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount. | `[]` | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b182ad2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1732 @@ +{ + "name": "reposilite", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reposilite", + "license": "MIT", + "devDependencies": { + "@bitnami/readme-generator-for-helm": "^2.5.0", + "markdownlint-cli": "^0.45.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/@bitnami/readme-generator-for-helm": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@bitnami/readme-generator-for-helm/-/readme-generator-for-helm-2.7.2.tgz", + "integrity": "sha512-7eXyJzxQTQj2ajpHlIhadciCCYWOqN8ieaweU25bStHOZowQ2c2CQyjO/bX4gxIf73LoRKxHhEYgLTllJY9SIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "commander": "^13.1.0", + "dot-object": "^2.1.5", + "lodash": "^4.17.21", + "markdown-table": "^2.0.0", + "yaml": "^2.7.0" + }, + "bin": { + "readme-generator": "bin/index.js" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dot-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.5.tgz", + "integrity": "sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^6.1.0", + "glob": "^7.1.6" + }, + "bin": { + "dot-object": "bin/dot-object" + } + }, + "node_modules/dot-object/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdownlint": { + "version": "0.38.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz", + "integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.45.0.tgz", + "integrity": "sha512-GiWr7GfJLVfcopL3t3pLumXCYs8sgWppjIA1F/Cc3zIMgD3tmkpyZ1xkm1Tej8mw53B93JsDjgA3KOftuYcfOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "~13.1.0", + "glob": "~11.0.2", + "ignore": "~7.0.4", + "js-yaml": "~4.1.0", + "jsonc-parser": "~3.3.1", + "jsonpointer": "~5.0.1", + "markdown-it": "~14.1.0", + "markdownlint": "~0.38.0", + "minimatch": "~10.0.1", + "run-con": "~1.3.2", + "smol-toml": "~1.3.4" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/markdownlint-cli/node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/run-con": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", + "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~4.1.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smol-toml": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.4.tgz", + "integrity": "sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cdb7b0a --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "reposilite-charts", + "homepage": "https://git.cryptic.systems/volker.raschek/reposilite-charts.git", + "license": "MIT", + "private": true, + "engineStrict": true, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "scripts": { + "readme:link": "markdown-link-check *.md", + "readme:lint": "markdownlint *.md -f", + "readme:parameters": "readme-generator -v values.yaml -r README.md" + }, + "devDependencies": { + "@bitnami/readme-generator-for-helm": "^2.5.0" + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..e9b3b26 --- /dev/null +++ b/renovate.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>volker.raschek/renovate-config:default#master", + "local>volker.raschek/renovate-config:container#master", + "local>volker.raschek/renovate-config:actions#master", + "local>volker.raschek/renovate-config:npm#master", + "local>volker.raschek/renovate-config:regexp#master" + ], + "customManagers": [ + { + "fileMatch": [ + "^Chart\\.yaml$" + ], + "matchStrings": [ + "appVersion: \"(?.*?)\"\\s+" + ], + "datasourceTemplate": "docker", + "depNameTemplate": "dzikoysk/reposilite", + "lookupNameTemplate": "docker/dzikoysk/reposilite", + "versioningTemplate": "semver" + }, + { + "fileMatch": ["^README\\.md$"], + "matchStrings": [ + "CHART_VERSION=(?.*)" + ], + "depNameTemplate": "dzikoysk/reposilite", + "packageNameTemplate": "https://github.com/dzikoysk/reposilite", + "datasourceTemplate": "git-tags", + "versioningTemplate": "semver" + } + ], + "packageRules": [ + { + "addLabels": [ + "renovate/automerge", + "renovate/documentation" + ], + "automerge": true, + "matchDepNames": [ + "dzikoysk/reposilite" + ], + "matchUpdateTypes": [ + "major", + "minor", + "patch" + ] + } + ] +} diff --git a/templates/_common.tpl b/templates/_common.tpl new file mode 100644 index 0000000..2755913 --- /dev/null +++ b/templates/_common.tpl @@ -0,0 +1,58 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "reposilite.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "reposilite.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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "reposilite.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common annotations +*/}} +{{- define "reposilite.annotations" -}} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "reposilite.labels" -}} +helm.sh/chart: {{ include "reposilite.chart" . }} +{{ include "reposilite.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "reposilite.selectorLabels" -}} +app.kubernetes.io/name: {{ include "reposilite.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + diff --git a/templates/_deployment.tpl b/templates/_deployment.tpl new file mode 100644 index 0000000..a50f17b --- /dev/null +++ b/templates/_deployment.tpl @@ -0,0 +1,75 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.deployment.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- if .Values.deployment.annotations }} +{{ toYaml .Values.deployment.annotations }} +{{- end }} +{{- end }} + + +{{/* env */}} + +{{- define "reposilite.deployment.reposilite.env" -}} +{{- $env := .Values.deployment.reposilite.env | default list }} +{{- if .Values.persistentVolumeClaim.enabled }} +{{- $env = concat $env (list (dict "name" "REPOSILITE_DATA" "value" .Values.persistentVolumeClaim.path )) }} +{{- end }} +{{ toYaml (dict "env" $env) }} +{{- end -}} + +REPOSILITE_DATA + +{{/* image */}} + +{{- define "reposilite.deployment.images.reposilite.fqin" -}} +{{- $registry := .Values.deployment.reposilite.image.registry -}} +{{- $repository := .Values.deployment.reposilite.image.repository -}} +{{- $tag := default .Chart.AppVersion .Values.deployment.reposilite.image.tag -}} +{{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- end -}} + +{{/* labels */}} + +{{- define "reposilite.deployment.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.deployment.labels }} +{{ toYaml .Values.deployment.labels }} +{{- end }} +{{- end }} + +{{/* serviceAccount */}} + +{{- define "reposilite.deployment.serviceAccount" -}} +{{- if .Values.serviceAccount.existing.enabled -}} +{{- printf "%s" .Values.serviceAccount.existing.serviceAccountName -}} +{{- else -}} +{{- include "reposilite.fullname" . -}} +{{- end -}} +{{- end }} + +{{/* volumeMounts */}} + +{{- define "reposilite.deployment.reposilite.volumeMounts" -}} +{{- $volumeMounts := .Values.deployment.reposilite.volumeMounts | default list }} +{{- if .Values.persistentVolumeClaim.enabled }} +{{- $volumeMounts = concat $volumeMounts (list (dict "name" "data" "mountPath" .Values.persistentVolumeClaim.path )) }} +{{- end }} +{{ toYaml (dict "volumeMounts" $volumeMounts) }} +{{- end -}} + +{{/* volumes */}} + +{{- define "reposilite.deployment.volumes" -}} +{{- $volumes := .Values.deployment.volumes | default list }} + +{{- if and .Values.persistentVolumeClaim.enabled (not .Values.persistentVolumeClaim.existing.enabled) }} +{{- $persistentVolumeClaimName := include "reposilite.persistentVolumeClaim.name" $ -}} +{{- $volumes = concat $volumes (list (dict "name" "data" "persistentVolumeClaim" (dict "claimName" $persistentVolumeClaimName))) }} +{{- end }} + +{{ toYaml (dict "volumes" $volumes) }} + +{{- end -}} \ No newline at end of file diff --git a/templates/_hpa.tpl b/templates/_hpa.tpl new file mode 100644 index 0000000..ef5fdab --- /dev/null +++ b/templates/_hpa.tpl @@ -0,0 +1,19 @@ +--- + +{{/* annotations */}} + +{{- define "reposilite.hpa.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- if .Values.hpa.annotations }} +{{ toYaml .Values.hpa.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.hpa.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.hpa.labels }} +{{ toYaml .Values.hpa.labels }} +{{- end }} +{{- end }} diff --git a/templates/_ingress.tpl b/templates/_ingress.tpl new file mode 100644 index 0000000..a02adef --- /dev/null +++ b/templates/_ingress.tpl @@ -0,0 +1,19 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.ingress.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- if .Values.ingress.annotations }} +{{ toYaml .Values.ingress.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.ingress.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.ingress.labels }} +{{ toYaml .Values.ingress.labels }} +{{- end }} +{{- end }} diff --git a/templates/_networkPolicy.tpl b/templates/_networkPolicy.tpl new file mode 100644 index 0000000..60dbd1b --- /dev/null +++ b/templates/_networkPolicy.tpl @@ -0,0 +1,19 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.networkPolicy.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- if .Values.networkPolicy.annotations }} +{{ toYaml .Values.networkPolicy.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.networkPolicy.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.networkPolicy.labels }} +{{ toYaml .Values.networkPolicy.labels }} +{{- end }} +{{- end }} diff --git a/templates/_persistentVolumeClaim.tpl b/templates/_persistentVolumeClaim.tpl new file mode 100644 index 0000000..5754363 --- /dev/null +++ b/templates/_persistentVolumeClaim.tpl @@ -0,0 +1,25 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.persistentVolumeClaim.annotations" -}} +helm.sh/resource-policy: keep +{{- if .Values.persistentVolumeClaim.new.annotations }} +{{ toYaml .Values.persistentVolumeClaim.new.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.persistentVolumeClaim.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.persistentVolumeClaim.new.labels }} +{{ toYaml .Values.persistentVolumeClaim.new.labels }} +{{- end }} +{{- end }} + +{{/* name */}} + +{{- define "reposilite.persistentVolumeClaim.name" -}} +{{ include "reposilite.fullname" . }} +{{- end }} \ No newline at end of file diff --git a/templates/_pod.tpl b/templates/_pod.tpl new file mode 100644 index 0000000..266df6d --- /dev/null +++ b/templates/_pod.tpl @@ -0,0 +1,17 @@ +--- + +{{/* annotations */}} + +{{- define "reposilite.pod.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.pod.labels" -}} +{{ include "reposilite.labels" . }} +{{- end }} + +{{- define "reposilite.pod.selectorLabels" -}} +{{ include "reposilite.selectorLabels" . }} +{{- end }} \ No newline at end of file diff --git a/templates/_serviceAccount.tpl b/templates/_serviceAccount.tpl new file mode 100644 index 0000000..4ade784 --- /dev/null +++ b/templates/_serviceAccount.tpl @@ -0,0 +1,17 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.serviceAccount.annotations" -}} +{{- if .Values.serviceAccount.new.annotations }} +{{ toYaml .Values.serviceAccount.new.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.serviceAccount.labels" -}} +{{- if .Values.serviceAccount.new.labels }} +{{ toYaml .Values.serviceAccount.new.labels }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/_services.tpl b/templates/_services.tpl new file mode 100644 index 0000000..dfb4d1f --- /dev/null +++ b/templates/_services.tpl @@ -0,0 +1,27 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* annotations */}} + +{{- define "reposilite.service.annotations" -}} +{{ include "reposilite.annotations" . }} +{{- if .Values.service.annotations }} +{{ toYaml .Values.service.annotations }} +{{- end }} +{{- end }} + +{{/* labels */}} + +{{- define "reposilite.service.labels" -}} +{{ include "reposilite.labels" . }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels }} +{{- end }} +{{- end }} + +{{/* names */}} + +{{- define "reposilite.service.name" -}} +{{- if .Values.service.enabled -}} +{{ include "reposilite.fullname" . }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/templates/deployment.yaml b/templates/deployment.yaml new file mode 100644 index 0000000..fe6b2f8 --- /dev/null +++ b/templates/deployment.yaml @@ -0,0 +1,140 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + {{- with (include "reposilite.deployment.annotations" . | fromYaml) }} + annotations: + {{- tpl (toYaml .) $ | nindent 4 }} + {{- end }} + {{- with (include "reposilite.deployment.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.deployment.replicas }} + selector: + matchLabels: + {{- include "reposilite.pod.selectorLabels" . | nindent 6 }} + {{- with .Values.deployment.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + template: + metadata: + {{- with (include "reposilite.pod.annotations" . | fromYaml) }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "reposilite.pod.labels" . | nindent 8 }} + spec: + {{- with .Values.deployment.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - {{- if gt (len .Values.deployment.reposilite.args) 0 }} + args: + {{- range .Values.deployment.reposilite.args }} + - {{ . | quote }} + {{- end }} + {{- end }} + {{- if gt (len .Values.deployment.reposilite.command) 0 }} + command: + {{- range .Values.deployment.reposilite.command }} + - {{ . | quote }} + {{- end }} + {{- end }} + {{- $env := (include "reposilite.deployment.reposilite.env" . | fromYaml) }} + {{- if and (hasKey $env "env") (gt (len $env.env) 0) }} + env: + {{- toYaml $env.env | nindent 8 }} + {{- end }} + {{- with .Values.deployment.reposilite.envFrom }} + envFrom: + {{- toYaml . | nindent 10 }} + {{- end }} + image: {{ include "reposilite.deployment.images.reposilite.fqin" . | quote }} + imagePullPolicy: {{ .Values.deployment.reposilite.image.pullPolicy }} + livenessProbe: + tcpSocket: + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 60 + successThreshold: 1 + timeoutSeconds: 3 + name: reposilite + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + readinessProbe: + tcpSocket: + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + {{- with .Values.deployment.reposilite.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.deployment.reposilite.securityContext }} + securityContext: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- $volumeMounts := (include "reposilite.deployment.reposilite.volumeMounts" . | fromYaml) }} + {{- if and (hasKey $volumeMounts "volumeMounts") (gt (len $volumeMounts.volumeMounts) 0) }} + volumeMounts: + {{- toYaml $volumeMounts.volumeMounts | nindent 8 }} + {{- end }} + {{- with .Values.deployment.dnsConfig }} + dnsConfig: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.deployment.dnsPolicy }} + dnsPolicy: {{ .Values.deployment.dnsPolicy }} + {{- end }} + {{- if .Values.deployment.hostname }} + hostname: {{ .Values.deployment.hostname }} + {{- end }} + hostNetwork: {{ .Values.deployment.hostNetwork }} + {{- with .Values.deployment.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.deployment.priorityClassName }} + priorityClassName: {{ .Values.deployment.priorityClassName }} + {{- end }} + {{- if .Values.deployment.restartPolicy }} + restartPolicy: {{ .Values.deployment.restartPolicy }} + {{- end }} + {{- with .Values.deployment.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccount: {{ include "reposilite.deployment.serviceAccount" . }} + {{- if .Values.deployment.subdomain }} + subdomain: {{ .Values.deployment.subdomain }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds }} + {{- with .Values.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.deployment.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- $volumes := (include "reposilite.deployment.volumes" . | fromYaml) }} + {{- if and (hasKey $volumes "volumes") (gt (len $volumes.volumes) 0) }} + volumes: + {{- toYaml $volumes.volumes | nindent 6 }} + {{- end }} \ No newline at end of file diff --git a/templates/hpa.yaml b/templates/hpa.yaml new file mode 100644 index 0000000..1815a88 --- /dev/null +++ b/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.hpa.enabled -}} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + {{- with (include "reposilite.hpa.annotations" . | fromYaml) }} + annotations: + {{- tpl (toYaml .) $ | nindent 4 }} + {{- end }} + {{- with (include "reposilite.hpa.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + {{- with .Values.hpa.behavior }} + behavior: + {{- toYaml . | nindent 4 }} + {{- end }} + metrics: + {{- toYaml .Values.hpa.metrics | nindent 2 }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + minReplicas: {{ .Values.hpa.minReplicas }} + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "reposilite.fullname" . }} +{{- end -}} \ No newline at end of file diff --git a/templates/ingress.yaml b/templates/ingress.yaml new file mode 100644 index 0000000..37c34c2 --- /dev/null +++ b/templates/ingress.yaml @@ -0,0 +1,45 @@ +{{- if .Values.ingress.enabled -}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- with (include "reposilite.ingress.annotations" . | fromYaml) }} + annotations: + {{- tpl (toYaml .) $ | nindent 4 }} + {{- end }} + {{- with (include "reposilite.ingress.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ tpl .host $ | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if .pathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + service: + name: {{ include "reposilite.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ tpl . $ | quote }} + {{- end }} + secretName: {{ .secretName | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/templates/networkPolicy.yaml b/templates/networkPolicy.yaml new file mode 100644 index 0000000..77487af --- /dev/null +++ b/templates/networkPolicy.yaml @@ -0,0 +1,32 @@ +{{- if .Values.networkPolicy.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + {{- with (include "reposilite.networkPolicy.annotations" . | fromYaml) }} + annotations: + {{- tpl (toYaml .) $ | nindent 4 }} + {{- end }} + {{- with (include "reposilite.networkPolicy.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + {{- include "reposilite.pod.selectorLabels" $ | nindent 6 }} + {{- with .Values.networkPolicy.policyTypes }} + policyTypes: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.networkPolicy.egress }} + egress: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.networkPolicy.ingress }} + ingress: + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/persistentVolumeClaim.yaml b/templates/persistentVolumeClaim.yaml new file mode 100644 index 0000000..15ddadf --- /dev/null +++ b/templates/persistentVolumeClaim.yaml @@ -0,0 +1,24 @@ +{{- if and .Values.persistentVolumeClaim.enabled (not .Values.persistentVolumeClaim.existing.enabled) -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- with (include "reposilite.persistentVolumeClaim.annotations" . | fromYaml) }} + annotations: + {{- tpl (toYaml .) $ | nindent 4 }} + {{- end }} + {{- with (include "reposilite.persistentVolumeClaim.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: + - {{ .Values.persistentVolumeClaim.new.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistentVolumeClaim.new.size | quote }} + {{- if .Values.persistentVolumeClaim.new.storageClass }} + storageClassName: {{ .Values.persistentVolumeClaim.new.storageClass | quote }} + {{- end }} +{{- end -}} \ No newline at end of file diff --git a/templates/service.yaml b/templates/service.yaml new file mode 100644 index 0000000..1672d39 --- /dev/null +++ b/templates/service.yaml @@ -0,0 +1,57 @@ +{{- if .Values.service.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + {{- with (include "reposilite.service.annotations" . | fromYaml) }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (include "reposilite.service.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.service.name" . }} + namespace: {{ .Release.Namespace }} +spec: + {{- if not (empty .Values.service.externalIPs) }} + externalIPs: + {{- range .Values.service.externalIPs }} + - {{ . }} + {{- end }} + {{- end }} + {{- if and (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) .Values.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} + {{- end }} + internalTrafficPolicy: {{ required "No internal traffic policy defined!" .Values.service.internalTrafficPolicy }} + {{- if .Values.service.ipFamilies }} + ipFamilies: + {{- range .Values.service.ipFamilies }} + - {{ . }} + {{- end }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerClass }} + loadBalancerClass: {{ .Values.service.loadBalancerClass }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if eq .Values.service.type "LoadBalancer" }} + loadBalancerSourceRanges: + {{- range .Values.service.loadBalancerSourceRanges }} + - {{ . }} + {{- end }} + {{- end }} + ports: + - name: http + protocol: TCP + port: {{ required "No service port defined!" .Values.service.port }} + selector: + {{- include "reposilite.pod.selectorLabels" . | nindent 4 }} + sessionAffinity: {{ required "No session affinity defined!" .Values.service.sessionAffinity }} + {{- with .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: + {{- toYaml . | nindent 4}} + {{- end }} + type: {{ required "No service type defined!" .Values.service.type }} +{{- end }} \ No newline at end of file diff --git a/templates/serviceAccount.yaml b/templates/serviceAccount.yaml new file mode 100644 index 0000000..cb85e56 --- /dev/null +++ b/templates/serviceAccount.yaml @@ -0,0 +1,25 @@ +{{- if not .Values.serviceAccount.existing.enabled }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "reposilite.serviceAccount.annotations" . | fromYaml) }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with (include "reposilite.serviceAccount.labels" . | fromYaml) }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ include "reposilite.fullname" . }} + namespace: {{ .Release.Namespace }} +automountServiceAccountToken: {{ .Values.serviceAccount.new.automountServiceAccountToken }} +{{- with .Values.serviceAccount.new.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- with .Values.serviceAccount.new.secrets }} +secrets: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/unittests/deployment/deployment.yaml b/unittests/deployment/deployment.yaml new file mode 100644 index 0000000..5b45354 --- /dev/null +++ b/unittests/deployment/deployment.yaml @@ -0,0 +1,447 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: Deployment template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/deployment.yaml +tests: +- it: Rendering default + set: {} + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: apps/v1 + kind: Deployment + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - equal: + path: spec.replicas + value: 1 + - isSubset: + path: spec.selector.matchLabels + content: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/name: reposilite + - equal: + path: spec.strategy + value: + type: "RollingUpdate" + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + - notExists: + path: spec.template.metadata.annotations + value: sadsdf + - equal: + path: spec.template.metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - notExists: + path: spec.template.spec.affinity + - notExists: + path: spec.template.spec.containers[0].args + - notExists: + path: spec.template.spec.containers[0].command + - contains: + path: spec.template.spec.containers[0].env + content: + name: JAVA_OPTS + value: "-Xmx64M" + - notExists: + path: spec.template.spec.containers[0].envFrom + - equal: + path: spec.template.spec.containers[0].image + value: docker.io/dzikoysk/reposilite:0.1.0 + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: IfNotPresent + - isSubset: + path: spec.template.spec.containers[0].livenessProbe + content: + tcpSocket: + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 60 + successThreshold: 1 + timeoutSeconds: 3 + - equal: + path: spec.template.spec.containers[0].name + value: reposilite + - contains: + path: spec.template.spec.containers[0].ports + content: + name: http + containerPort: 8080 + protocol: TCP + - isSubset: + path: spec.template.spec.containers[0].readinessProbe + content: + tcpSocket: + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + - notExists: + path: spec.template.spec.containers[0].resources + - notExists: + path: spec.template.spec.containers[0].securityContext + - notExists: + path: spec.template.spec.containers[0].volumeMounts + - notExists: + path: spec.template.spec.dnsConfig + - notExists: + path: spec.template.spec.dnsPolicy + - notExists: + path: spec.template.spec.hostname + - equal: + path: spec.template.spec.hostNetwork + value: false + - notExists: + path: spec.template.spec.imagePullSecrets + - notExists: + path: spec.template.spec.initContainers + - notExists: + path: spec.template.spec.nodeSelector + - notExists: + path: spec.template.spec.priorityClassName + - notExists: + path: spec.template.spec.restartPolicy + - notExists: + path: spec.template.spec.subdomain + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 60 + - notExists: + path: spec.template.spec.tolerations + - notExists: + path: spec.template.spec.topologySpreadConstraints + - notExists: + path: spec.template.spec.volumes + +- it: Test custom replicas + set: + deployment.replicas: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + +- it: Test custom strategy + set: + deployment.strategy: + type: "RollingUpdate" + rollingUpdate: + maxSurge: 10 + maxUnavailable: 5 + asserts: + - equal: + path: spec.strategy + value: + type: "RollingUpdate" + rollingUpdate: + maxSurge: 10 + maxUnavailable: 5 + +- it: Test custom affinity + set: + deployment.affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: topology.kubernetes.io/zone + operator: In + values: + - antarctica-east1 + - antarctica-west1 + asserts: + - equal: + path: spec.template.spec.affinity + value: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: topology.kubernetes.io/zone + operator: In + values: + - antarctica-east1 + - antarctica-west1 + +- it: Test additional arguments + set: + deployment.reposilite.args: + - "--foo=bar" + - "--bar=foo" + asserts: + - equal: + path: spec.template.spec.containers[0].args + value: + - --foo=bar + - --bar=foo + +- it: Test additional commands + set: + deployment.reposilite.command: + - "/bin/bash" + asserts: + - equal: + path: spec.template.spec.containers[0].command + value: + - /bin/bash + +- it: Test custom imageRegistry and imageRepository + set: + deployment.reposilite.image.registry: registry.example.local + deployment.reposilite.image.repository: path/special/reposilite + deployment.reposilite.image.tag: 2.0.0 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: registry.example.local/path/special/reposilite:2.0.0 + +- it: Test custom imagePullPolicy + set: + deployment.reposilite.image.pullPolicy: Always + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: Always + +- it: Test custom port + set: + service.port: 8443 + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: http + containerPort: 8443 + protocol: TCP + +- it: Test custom resources + set: + deployment.reposilite.resources: + limits: + cpu: 100m + memory: 250MB + requests: + cpu: 25m + memory: 100MB + asserts: + - equal: + path: spec.template.spec.containers[0].resources + value: + limits: + cpu: 100m + memory: 250MB + requests: + cpu: 25m + memory: 100MB + +- it: Test custom securityContext + set: + deployment.reposilite.securityContext: + capabilities: + add: + - NET_RAW + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext + value: + capabilities: + add: + - NET_RAW + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +- it: Test custom volumeMounts + set: + deployment.reposilite.volumeMounts: + - name: data + mountPath: /usr/lib/data + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: data + mountPath: /usr/lib/data + +- it: Test dnsConfig + set: + deployment.dnsConfig: + nameservers: + - "8.8.8.8" + - "8.8.4.4" + asserts: + - equal: + path: spec.template.spec.dnsConfig + value: + nameservers: + - "8.8.8.8" + - "8.8.4.4" + +- it: Test dnsPolicy + set: + deployment.dnsPolicy: ClusterFirst + asserts: + - equal: + path: spec.template.spec.dnsPolicy + value: ClusterFirst + +- it: Test hostNetwork, hostname, subdomain + set: + deployment.hostNetwork: true + deployment.hostname: pg-exporter + deployment.subdomain: exporters.internal + asserts: + - equal: + path: spec.template.spec.hostNetwork + value: true + - equal: + path: spec.template.spec.hostname + value: pg-exporter + - equal: + path: spec.template.spec.subdomain + value: exporters.internal + +- it: Test imagePullSecrets + set: + deployment.imagePullSecrets: + - name: my-pull-secret + - name: my-special-secret + asserts: + - equal: + path: spec.template.spec.imagePullSecrets + value: + - name: my-pull-secret + - name: my-special-secret + +- it: Test nodeSelector + set: + deployment.nodeSelector: + foo: bar + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + foo: bar + +- it: Test priorityClassName + set: + deployment.priorityClassName: my-priority + asserts: + - equal: + path: spec.template.spec.priorityClassName + value: my-priority + +- it: Test restartPolicy + set: + deployment.restartPolicy: Always + asserts: + - equal: + path: spec.template.spec.restartPolicy + value: Always + +- it: Test custom securityContext + set: + deployment.securityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + asserts: + - equal: + path: spec.template.spec.securityContext + value: + fsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + +- it: Test terminationGracePeriodSeconds + set: + deployment.terminationGracePeriodSeconds: 120 + asserts: + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 120 + +- it: Test tolerations + set: + deployment.tolerations: + - key: hdd/type + operator: Equal + value: ssd + effect: NoSchedule + asserts: + - equal: + path: spec.template.spec.tolerations + value: + - key: hdd/type + operator: Equal + value: ssd + effect: NoSchedule + +- it: Test topologySpreadConstraints + set: + deployment.topologySpreadConstraints: + - topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/instance: reposilite + asserts: + - equal: + path: spec.template.spec.topologySpreadConstraints + value: + - topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/instance: reposilite + +- it: Test additional volumes + set: + deployment.volumes: + - name: data + hostPath: + path: /usr/lib/data + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: data + hostPath: + path: /usr/lib/data diff --git a/unittests/deployment/mountPersistentVolumeClaim.yaml b/unittests/deployment/mountPersistentVolumeClaim.yaml new file mode 100644 index 0000000..7b58b1b --- /dev/null +++ b/unittests/deployment/mountPersistentVolumeClaim.yaml @@ -0,0 +1,52 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: Mount persistent volume claim +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/deployment.yaml +tests: +- it: Rendering default volumes and volumeMounts with persistent volume claim + set: + persistentVolumeClaim.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REPOSILITE_DATA + value: /app/data + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: data + mountPath: /app/data + - contains: + path: spec.template.spec.volumes + content: + name: data + persistentVolumeClaim: + claimName: reposilite-unittest + +- it: Rendering custom volumes and volumeMounts with persistent volume claim + set: + persistentVolumeClaim.enabled: true + persistentVolumeClaim.path: /usr/lib/reposilite/data + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REPOSILITE_DATA + value: /usr/lib/reposilite/data + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: data + mountPath: /usr/lib/reposilite/data + - contains: + path: spec.template.spec.volumes + content: + name: data + persistentVolumeClaim: + claimName: reposilite-unittest diff --git a/unittests/hpa/hpa.yaml b/unittests/hpa/hpa.yaml new file mode 100644 index 0000000..9d9056f --- /dev/null +++ b/unittests/hpa/hpa.yaml @@ -0,0 +1,152 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: HPA template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/hpa.yaml +tests: +- it: Skip rendering by default. + asserts: + - hasDocuments: + count: 0 + +- it: Rendering when enabled - default + set: + hpa.enabled: true + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - isSubset: + path: spec.behavior + content: + scaleDown: + policies: + - type: Pods + value: 1 + periodSeconds: 60 + stabilizationWindowSeconds: 300 + - lengthEqual: + path: spec.metrics + count: 2 + - contains: + path: spec.metrics + content: + resource: + name: cpu + target: + averageUtilization: 65 + type: Utilization + type: Resource + - contains: + path: spec.metrics + content: + resource: + name: memory + target: + averageUtilization: 65 + type: Utilization + type: Resource + - equal: + path: spec.maxReplicas + value: 10 + - equal: + path: spec.minReplicas + value: 1 + - equal: + path: spec.scaleTargetRef + value: + apiVersion: apps/v1 + kind: Deployment + name: reposilite-unittest + +- it: Rendering when enabled - custom values + set: + hpa.enabled: true + hpa.annotations: + foo: bar + hpa.labels: + bar: foo + hpa.maxReplicas: 25 + hpa.minReplicas: 5 + hpa.metrics: + - resource: + name: cpu + target: + averageUtilization: 60 + type: Utilization + type: Resource + - resource: + name: memory + target: + averageUtilization: 60 + type: Utilization + type: Resource + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + name: reposilite-unittest + namespace: testing + - equal: + path: metadata.annotations + value: + foo: bar + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + bar: foo + helm.sh/chart: reposilite-0.1.0 + - contains: + path: spec.metrics + content: + resource: + name: cpu + target: + averageUtilization: 60 + type: Utilization + type: Resource + - contains: + path: spec.metrics + content: + resource: + name: memory + target: + averageUtilization: 60 + type: Utilization + type: Resource + - equal: + path: spec.maxReplicas + value: 25 + - equal: + path: spec.minReplicas + value: 5 + - equal: + path: spec.scaleTargetRef + value: + apiVersion: apps/v1 + kind: Deployment + name: reposilite-unittest \ No newline at end of file diff --git a/unittests/ingress/http.yaml b/unittests/ingress/http.yaml new file mode 100644 index 0000000..bffa8b1 --- /dev/null +++ b/unittests/ingress/http.yaml @@ -0,0 +1,110 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: Ingress HTTP template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/ingress.yaml +tests: +- it: Skip rendering when disabled + set: + ingress.enabled: false + asserts: + - hasDocuments: + count: 0 + +- it: Rendering default values + set: + ingress.enabled: true + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - equal: + path: spec.ingressClassName + value: nginx + - isNullOrEmpty: + path: spec.rules + - notExists: + path: spec.tls + +- it: Render custom annotations and labels + set: + ingress.annotations: + foo: bar + ingress.enabled: true + ingress.labels: + foo: bar + asserts: + - equal: + path: metadata.annotations + value: + foo: bar + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + foo: bar + helm.sh/chart: reposilite-0.1.0 + +- it: Render custom HTTP path and TLS + set: + ingress.enabled: true + ingress.hosts: + - host: reposilite.example.local + paths: + - path: / + pathType: Prefix + ingress.tls: + - secretName: reposilite-http-tls + hosts: + - reposilite.example.local + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: reposilite-unittest + namespace: testing + - equal: + path: spec.ingressClassName + value: nginx + - contains: + path: spec.rules + content: + host: reposilite.example.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: reposilite-unittest + port: + number: 8080 + - contains: + path: spec.tls + content: + hosts: + - reposilite.example.local + secretName: reposilite-http-tls diff --git a/unittests/networkPolicy/networkPolicy.yaml b/unittests/networkPolicy/networkPolicy.yaml new file mode 100644 index 0000000..ba803ca --- /dev/null +++ b/unittests/networkPolicy/networkPolicy.yaml @@ -0,0 +1,98 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: NetworkPolicy template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/networkPolicy.yaml +tests: +- it: Skip rendering networkPolicy + set: + networkPolicy.enabled: false + asserts: + - hasDocuments: + count: 0 + +- it: Render default networkPolicy + set: + networkPolicy.enabled: true + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - equal: + path: spec.podSelector.matchLabels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/name: reposilite + - notExists: + path: spec.policyTypes + - notExists: + path: spec.egress + - notExists: + path: spec.ingress + +- it: Template networkPolicy with policyTypes, egress and ingress configuration + set: + networkPolicy.enabled: true + networkPolicy.policyTypes: + - Egress + - Ingress + networkPolicy.ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + podSelector: + matchLabels: + app.kubernetes.io/name: prometheus + networkPolicy.egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + asserts: + - equal: + path: spec.policyTypes + value: + - Egress + - Ingress + - equal: + path: spec.egress + value: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + - equal: + path: spec.ingress + value: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + podSelector: + matchLabels: + app.kubernetes.io/name: prometheus diff --git a/unittests/persistentVolumeClaim/persistentVolumeClaim.yaml b/unittests/persistentVolumeClaim/persistentVolumeClaim.yaml new file mode 100644 index 0000000..82dca64 --- /dev/null +++ b/unittests/persistentVolumeClaim/persistentVolumeClaim.yaml @@ -0,0 +1,97 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: PersistentVolumeClaim template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/persistentVolumeClaim.yaml +tests: +- it: Skip rendering persistentVolumeClaim, when it's disabled in general + set: + persistentVolumeClaim.enabled: false + asserts: + - hasDocuments: + count: 0 + +- it: Skip rendering persistentVolumeClaim, when using an existing persistentVolumeClaim + set: + persistentVolumeClaim.enabled: true + persistentVolumeClaim.existing.enabled: true + asserts: + - hasDocuments: + count: 0 + +- it: Render default persistentVolumeClaim + set: + persistentVolumeClaim.enabled: true + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: v1 + kind: PersistentVolumeClaim + name: reposilite-unittest + namespace: testing + - equal: + path: metadata.annotations + value: + helm.sh/resource-policy: keep + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - contains: + path: spec.accessModes + content: + ReadWriteOnce + - equal: + path: spec.resources.requests.storage + value: 10Gi + +- it: Test custom annotations and labels + set: + persistentVolumeClaim.enabled: true + persistentVolumeClaim.new.annotations: + foo: bar + persistentVolumeClaim.new.labels: + foo: bar + asserts: + - equal: + path: metadata.annotations + value: + foo: bar + helm.sh/resource-policy: keep + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + foo: bar + helm.sh/chart: reposilite-0.1.0 + +- it: Test custom access mode + set: + persistentVolumeClaim.enabled: true + persistentVolumeClaim.new.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: + ReadWriteMany + +- it: Test custom size + set: + persistentVolumeClaim.enabled: true + persistentVolumeClaim.new.size: 50Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 50Gi \ No newline at end of file diff --git a/unittests/serviceAccount/serviceAccount.yaml b/unittests/serviceAccount/serviceAccount.yaml new file mode 100644 index 0000000..7157e45 --- /dev/null +++ b/unittests/serviceAccount/serviceAccount.yaml @@ -0,0 +1,79 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: ServiceAccount reposilite template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/serviceAccount.yaml +tests: +- it: Skip rendering. + set: + serviceAccount.existing.enabled: true + asserts: + - hasDocuments: + count: 0 + +- it: Rendering serviceAccount with default values. + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: v1 + kind: ServiceAccount + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - notExists: + path: metadata.labels + - equal: + path: automountServiceAccountToken + value: true + - notExists: + path: imagePullSecrets + - notExists: + path: secrets + + +- it: Rendering serviceAccount with custom values. + set: + serviceAccount.new.annotations: + foo: bar + serviceAccount.new.labels: + bar: foo + serviceAccount.new.automountServiceAccountToken: false + serviceAccount.new.imagePullSecrets: + - name: "my-pull-secret" + serviceAccount.new.secrets: + - name: "my-secret" + namespace: "my-namespace" + fieldPath: "my-path" + asserts: + - hasDocuments: + count: 1 + - exists: + path: metadata.annotations + value: + foo: bar + - exists: + path: metadata.labels + value: + bar: foo + - equal: + path: metadata.name + value: reposilite-unittest + - equal: + path: automountServiceAccountToken + value: false + - equal: + path: imagePullSecrets + value: + - name: "my-pull-secret" + - equal: + path: secrets + value: + - name: "my-secret" + namespace: "my-namespace" + fieldPath: "my-path" diff --git a/unittests/services/service.yaml b/unittests/services/service.yaml new file mode 100644 index 0000000..6bfd465 --- /dev/null +++ b/unittests/services/service.yaml @@ -0,0 +1,172 @@ +chart: + appVersion: 0.1.0 + version: 0.1.0 +suite: Service http template +release: + name: reposilite-unittest + namespace: testing +templates: +- templates/service.yaml +tests: +- it: Skip service when disabled. + set: + service.enabled: false + asserts: + - hasDocuments: + count: 0 + +- it: Rendering service with default values. + asserts: + - hasDocuments: + count: 1 + - containsDocument: + apiVersion: v1 + kind: Service + name: reposilite-unittest + namespace: testing + - notExists: + path: metadata.annotations + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + - notExists: + path: spec.externalIPs + - notExists: + path: spec.externalTrafficPolicy + - equal: + path: spec.internalTrafficPolicy + value: Cluster + - notExists: + path: spec.ipFamilies + - notExists: + path: spec.loadBalancerClass + - notExists: + path: spec.loadBalancerIP + - notExists: + path: spec.loadBalancerSourceRanges + - equal: + path: spec.ports[0].name + value: http + - equal: + path: spec.ports[0].protocol + value: TCP + - equal: + path: spec.ports[0].port + value: 8080 + - equal: + path: spec.selector + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/name: reposilite + - equal: + path: spec.sessionAffinity + value: None + - notExists: + path: spec.sessionAffinityConfig + - equal: + path: spec.type + value: ClusterIP + +- it: Require internalTrafficPolicy. + set: + service.internalTrafficPolicy: "" + asserts: + - failedTemplate: + errorMessage: No internal traffic policy defined! + +- it: Require port. + set: + service.port: "" + asserts: + - failedTemplate: + errorMessage: No service port defined! + +- it: Require sessionAffinity. + set: + service.sessionAffinity: "" + asserts: + - failedTemplate: + errorMessage: No session affinity defined! + +- it: Require service type. + set: + service.type: "" + asserts: + - failedTemplate: + errorMessage: No service type defined! + +- it: Render service with custom annotations and labels. + set: + service.annotations: + foo: bar + service.labels: + bar: foo + asserts: + - equal: + path: metadata.annotations + value: + foo: bar + - equal: + path: metadata.labels + value: + app.kubernetes.io/instance: reposilite-unittest + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: reposilite + app.kubernetes.io/version: 0.1.0 + helm.sh/chart: reposilite-0.1.0 + bar: foo + +- it: Change defaults + set: + service.externalIPs: + - "10.11.12.13/32" + service.externalTrafficPolicy: Local + service.internalTrafficPolicy: Local + service.ipFamilies: + - IPv4 + service.loadBalancerClass: aws + service.loadBalancerIP: "11.12.13.14" + service.loadBalancerSourceRanges: + - "11.12.0.0/17" + service.port: 10443 + service.sessionAffinity: ClientIP + service.type: LoadBalancer + asserts: + - equal: + path: spec.externalIPs + value: + - 10.11.12.13/32 + - equal: + path: spec.externalTrafficPolicy + value: Local + - equal: + path: spec.internalTrafficPolicy + value: Local + - equal: + path: spec.ipFamilies + value: + - IPv4 + - equal: + path: spec.loadBalancerClass + value: aws + - equal: + path: spec.loadBalancerIP + value: "11.12.13.14" + - equal: + path: spec.loadBalancerSourceRanges + value: + - "11.12.0.0/17" + - equal: + path: spec.ports[0].port + value: 10443 + - equal: + path: spec.sessionAffinity + value: ClientIP + - equal: + path: spec.type + value: LoadBalancer \ No newline at end of file diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..1a0af50 --- /dev/null +++ b/values.yaml @@ -0,0 +1,412 @@ +# Declare variables to be passed into your templates. + +## @section Global +## @param nameOverride Individual release name suffix. +## @param fullnameOverride Override the complete release name logic. +nameOverride: "" +fullnameOverride: "" + +## @section Deployment +deployment: + ## @param deployment.annotations Additional deployment annotations. + ## @param deployment.labels Additional deployment labels. + annotations: {} + labels: {} + + ## @param deployment.additionalContainers List of additional containers. + additionalContainers: [] + # - command: [ "sh", "-c", "echo hello world" ] + # image: "docker.io/library/busybox:latest" + # name: side-car + + ## @param deployment.affinity Affinity for the Reposilite deployment. + affinity: {} + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: kubernetes.io/os + # operator: In + # values: + # - linux + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 20 + # preference: + # matchExpressions: + # - key: kubernetes.io/arch + # operator: In + # values: + # - amd64 + + ## @param deployment.initContainers List of additional init containers. + initContainers: [] + # - command: [ "sh", "-c", "echo hello world" ] + # image: "docker.io/library/busybox:latest" + # name: init + + ## @param deployment.dnsConfig dnsConfig of the Reposilite deployment. + dnsConfig: {} + # nameservers: + # - 192.0.2.1 # this is an example + # searches: + # - ns1.svc.cluster-domain.example + # - my.dns.search.suffix + # options: + # - name: ndots + # value: "2" + # - name: edns0 + + ## @param deployment.dnsPolicy dnsPolicy of the Reposilite deployment. + dnsPolicy: "" + + ## @param deployment.hostname Individual hostname of the pod. + ## @param deployment.subdomain Individual domain of the pod. + hostname: "" + subdomain: "" + + ## @param deployment.hostNetwork Use the kernel network namespace of the host system. + hostNetwork: false + + ## @param deployment.imagePullSecrets Secret to use for pulling the image. + imagePullSecrets: [] + # - name: "my-custom-secret" + + reposilite: + ## @param deployment.reposilite.args Arguments passed to the Reposilite container. + args: [] + # - "-c" + # - "echo HelloWorld" + + ## @param deployment.reposilite.command Command passed to the Reposilite container. + command: [] + # - "/bin/bash" + + ## @extra deployment.reposilite.env List of environment variables for the Reposilite container. + ## @skip deployment.reposilite.env[0].name JAVA_OPTS: Environment variable to define custom java options. + ## @skip deployment.reposilite.env[0].value Set custom java options. Use `-Xmx64M` to reduce the heap of the JVM. + env: + - name: JAVA_OPTS + value: "-Xmx64M" + # - name: SPECIAL_ENV_A + # value: special-key + # - name: SPECIAL_ENV + # valueFrom: + # configMapKeyRef: + # name: special-config + # key: special-key + # - name: SPECIAL_ENV + # valueFrom: + # secretKeyRef: + # name: special-secret + # key: special-key + + ## @param deployment.reposilite.envFrom List of environment variables mounted from configMaps or secrets for the Reposilite container. + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: special-secret + + ## @param deployment.reposilite.image.registry Image registry, eg. `docker.io`. + ## @param deployment.reposilite.image.repository Image repository, eg. `library/busybox`. + ## @param deployment.reposilite.image.tag Custom image tag, eg. `0.1.0`. Defaults to `appVersion`. + ## @param deployment.reposilite.image.pullPolicy Image pull policy. + image: + registry: docker.io + repository: dzikoysk/reposilite + tag: "" + pullPolicy: IfNotPresent + + ## @param deployment.reposilite.resources CPU and memory resources of the pod. + resources: {} + # limits: + # cpu: + # ephemeral-storage: + # memory: + # requests: + # cpu: + # ephemeral-storage: + # memory: + + ## @param deployment.reposilite.securityContext Security context of the container of the deployment. + securityContext: {} + # capabilities: + # add: + # - NET_RAW + # drop: + # - ALL + # privileged: false + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + ## @param deployment.reposilite.volumeMounts Additional volume mounts. + volumeMounts: [] + # - name: my-configmap-volume + # mountPath: /configmap + # readOnly: true + + ## @param deployment.nodeSelector NodeSelector of the Reposilite deployment. + nodeSelector: {} + + ## @param deployment.priorityClassName PriorityClassName of the Reposilite deployment. + priorityClassName: "" + + ## @param deployment.replicas Number of replicas for the Reposilite deployment. + replicas: 1 + + ## @param deployment.restartPolicy Restart policy of the Reposilite deployment. + restartPolicy: "" + + ## @param deployment.securityContext Security context of the Reposilite deployment. + securityContext: {} + # fsGroup: 2000 + + ## @param deployment.strategy.type Strategy type - `Recreate` or `RollingUpdate`. + ## @param deployment.strategy.rollingUpdate.maxSurge The maximum number of pods that can be scheduled above the desired number of pods during a rolling update. + ## @param deployment.strategy.rollingUpdate.maxUnavailable The maximum number of pods that can be unavailable during a rolling update. + strategy: + type: "RollingUpdate" + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + + ## @param deployment.terminationGracePeriodSeconds How long to wait until forcefully kill the pod. + terminationGracePeriodSeconds: 60 + + ## @param deployment.tolerations Tolerations of the Reposilite deployment. + tolerations: [] + # - key: host.kubernetes.io/ssd + # operator: Equal + # value: "true" + # effect: NoSchedule + + ## @param deployment.topologySpreadConstraints TopologySpreadConstraints of the Reposilite deployment. + topologySpreadConstraints: [] + # - topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/instance: prometheus-reposilite + + ## @param deployment.volumes Additional volumes to mount into the pods of the prometheus-exporter deployment. + volumes: [] + # - name: my-configmap-volume + # config: + # name: my-configmap + # - name: my-secret-volume + # secret: + # secretName: my-secret + + +## @section Horizontal Pod Autoscaler (HPA) +# In order for the HPA to function successfully, a metric server is required, especially for resource consumption. The +# metric server enables the CPU and memory utilisation to be recorded. If such a metric server is not available, the HPA +# cannot scale pods based on CPU or memory utilisation. Further information be be found here: +# https://github.com/kubernetes-sigs/metrics-server#deployment +hpa: + ## @param hpa.enabled Enable the horizontal pod autoscaler (HPA). + ## @param hpa.annotations Additional annotations for the HPA. + ## @param hpa.labels Additional labels for the HPA. + ## @extra hpa.behavior.scaleDown Scaling policy for scaling down. + ## @skip hpa.behavior.scaleDown.policies Skip individual scale down policies. + ## @skip hpa.behavior.scaleDown.stabilizationWindowSeconds Skip individual stabilizationWindowSeconds. + ## @param hpa.metrics Metrics contains the specifications for which to use to calculate the desired replica count. + ## @skip hpa.metrics Skip individual HPA metric configurations. + ## @param hpa.minReplicas Min replicas is the lower limit for the number of replicas to which the autoscaler can scale down. + ## @param hpa.maxReplicas Upper limit for the number of pods that can be set by the autoscaler. + enabled: false + annotations: {} + labels: {} + behavior: + scaleDown: + policies: + - type: Pods + value: 1 + periodSeconds: 60 + stabilizationWindowSeconds: 300 + metrics: + - resource: + name: cpu + target: + averageUtilization: 65 + type: Utilization + type: Resource + - resource: + name: memory + target: + averageUtilization: 65 + type: Utilization + type: Resource + minReplicas: 1 + maxReplicas: 10 + + +## @section Ingress +ingress: + ## @param ingress.enabled Enable creation of an ingress resource. Requires, that the http service is also enabled. + ## @param ingress.className Ingress class. + ## @param ingress.annotations Additional ingress annotations. + ## @param ingress.labels Additional ingress labels. + enabled: false + className: "nginx" + annotations: {} + labels: {} + + ## @param ingress.hosts Ingress specific configuration. Let ingress controller listen on specific DNS names and path's. + ## @skip ingress.hosts Skip individual host configuration. + hosts: [] + # - host: reposilite.example.local + # paths: + # - path: / + # pathType: Prefix + + ## @param ingress.tls Ingress specific TLS configuration. Use TLS encryption for specific hostnames. + ## @skip ingress.tls Skip individual TLS configuration. + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + +## @section Network Policy +networkPolicy: + ## @param networkPolicy.enabled Enable network policies in general. + ## @param networkPolicy.annotations Additional network policy annotations. + ## @param networkPolicy.labels Additional network policy labels. + ## @param networkPolicy.policyTypes List of policy types. Supported is ingress, egress or ingress and egress. + ## @param networkPolicy.egress Concrete egress network policy implementation. + ## @skip networkPolicy.egress Skip individual egress configuration. + ## @param networkPolicy.ingress Concrete ingress network policy implementation. + ## @skip networkPolicy.ingress Skip individual ingress configuration. + enabled: false + annotations: {} + labels: {} + policyTypes: [] + # - Egress + # - Ingress + egress: [] + # Allow outgoing DNS traffic to the internal running DNS-Server. For example core-dns. + # + # - to: + # - namespaceSelector: + # matchLabels: + # kubernetes.io/metadata.name: kube-system + # podSelector: + # matchLabels: + # k8s-app: kube-dns + # ports: + # - port: 53 + # protocol: TCP + # - port: 53 + # protocol: UDP + + ingress: [] + # Allow incoming HTTP traffic from prometheus. + # + # - from: + # - namespaceSelector: + # matchLabels: + # kubernetes.io/metadata.name: monitoring + # podSelector: + # matchLabels: + # app.kubernetes.io/name: prometheus + # ports: + # - port: http + # protocol: TCP + + # Allow incoming HTTP traffic from ingress-nginx. + # + # - from: + # - namespaceSelector: + # matchLabels: + # kubernetes.io/metadata.name: ingress-nginx + # podSelector: + # matchLabels: + # app.kubernetes.io/name: ingress-nginx + # ports: + # - port: http + # protocol: TCP + + +## @section Persistent Volume Claim +persistentVolumeClaim: + ## @param persistentVolumeClaim.enabled Enable persistence in general. + ## @param persistentVolumeClaim.path Path where the persistent volume claim should be mounted into the container file system. + enabled: false + path: /app/data + + ## @param persistentVolumeClaim.existing.enabled Enable an existing persistent volume claim. + ## @param persistentVolumeClaim.existing.persistentVolumeClaimName Name of the existing persistent volume claim. + existing: + enabled: false + persistentVolumeClaimName: "" + + ## @param persistentVolumeClaim.new.annotations Additional service account annotations. + ## @param persistentVolumeClaim.new.labels Additional service account labels. + ## @param persistentVolumeClaim.new.accessMode Access mode of the persistent volume claim. More information about the access modes are in detail documented [online](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes). + ## @param persistentVolumeClaim.new.size Size of the persistent volume claim. + ## @param persistentVolumeClaim.new.storageClass Custom storage class. Left it empty to use the clusters default storage class. + new: + annotations: {} + labels: {} + accessMode: ReadWriteOnce + size: 10Gi + storageClass: "" + + +## @section Service +## @param service.enabled Enable the service. +## @param service.annotations Additional service annotations. +## @param service.externalIPs External IPs for the service. +## @param service.externalTrafficPolicy If `service.type` is `NodePort` or `LoadBalancer`, set this to `Local` to tell kube-proxy to only use node local endpoints for cluster external traffic. Furthermore, this enables source IP preservation. +## @param service.internalTrafficPolicy If `service.type` is `NodePort` or `LoadBalancer`, set this to `Local` to tell kube-proxy to only use node local endpoints for cluster internal traffic. +## @param service.ipFamilies IPFamilies is list of IP families (e.g. `IPv4`, `IPv6`) assigned to this service. This field is usually assigned automatically based on cluster configuration and only required for customization. +## @param service.labels Additional service labels. +## @param service.loadBalancerClass LoadBalancerClass is the class of the load balancer implementation this Service belongs to. Requires service from type `LoadBalancer`. +## @param service.loadBalancerIP LoadBalancer will get created with the IP specified in this field. Requires service from type `LoadBalancer`. +## @param service.loadBalancerSourceRanges Source range filter for LoadBalancer. Requires service from type `LoadBalancer`. +## @param service.port Port to forward the traffic to. +## @param service.sessionAffinity Supports `ClientIP` and `None`. Enable client IP based session affinity via `ClientIP`. +## @param service.sessionAffinityConfig Contains the configuration of the session affinity. +## @param service.type Kubernetes service type for the traffic. +service: + enabled: true + annotations: {} + externalIPs: [] + externalTrafficPolicy: "Cluster" + internalTrafficPolicy: "Cluster" + ipFamilies: [] + labels: {} + loadBalancerClass: "" + loadBalancerIP: "" + loadBalancerSourceRanges: [] + port: 8080 + sessionAffinity: "None" + sessionAffinityConfig: {} + type: "ClusterIP" + + +## @section ServiceAccount +serviceAccount: + ## @param serviceAccount.existing.enabled Use an existing service account instead of creating a new one. Assumes that the user has all the necessary kubernetes API authorizations. + ## @param serviceAccount.existing.serviceAccountName Name of the existing service account. + existing: + enabled: false + serviceAccountName: "" + + ## @param serviceAccount.new.annotations Additional service account annotations. + ## @param serviceAccount.new.labels Additional service account labels. + ## @param serviceAccount.new.automountServiceAccountToken Enable/disable auto mounting of the service account token. + ## @param serviceAccount.new.imagePullSecrets ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images in pods that reference this serviceAccount. + ## @param serviceAccount.new.secrets Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount. + new: + annotations: {} + labels: {} + automountServiceAccountToken: true + imagePullSecrets: [] + # - name: "my-image-pull-secret" + secrets: [] + # - name: "my-secret" + # namespace: "my-namespace" + # fieldPath: "my-field"