From c40d249084a51b7477b4df39348c4ec99a99ecf8 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:42:32 +0200 Subject: [PATCH] pihole - migrate library (#2153) * pihole - migrate library * add templates * ci values * add some caps * update emta * add some caps * service * update ui * format * add initial migration * no need to carry them on, those are not used anywhere * make dhcp a dict * remove test code * no cidr * up version * keep version * safely access keys * typo --- library/ix-dev/charts/pihole/Chart.lock | 8 +- library/ix-dev/charts/pihole/Chart.yaml | 6 +- library/ix-dev/charts/pihole/README.md | 4 +- library/ix-dev/charts/pihole/app-readme.md | 4 +- .../charts/pihole/charts/common-1.2.9.tgz | Bin 0 -> 63211 bytes .../charts/pihole/charts/common-2304.0.1.tgz | Bin 4994 -> 0 bytes .../ix-dev/charts/pihole/ci/basic-values.yaml | 16 + .../charts/pihole/ci/hostnet-values.yaml | 22 - .../charts/pihole/ci/nodhcp-values.yaml | 13 + .../ix-dev/charts/pihole/ci/test-values.yaml | 20 - library/ix-dev/charts/pihole/metadata.yaml | 18 +- .../ix-dev/charts/pihole/migrations/migrate | 95 +++ library/ix-dev/charts/pihole/questions.yaml | 628 ++++++++++++------ .../ix-dev/charts/pihole/templates/NOTES.txt | 1 + .../charts/pihole/templates/_migration.tpl | 35 + .../charts/pihole/templates/_persistence.tpl | 33 + .../charts/pihole/templates/_pihole.tpl | 61 ++ .../charts/pihole/templates/_portal.tpl | 12 + .../charts/pihole/templates/_service.tpl | 33 + .../charts/pihole/templates/common.yaml | 13 + .../charts/pihole/templates/deployment.yaml | 110 --- .../pihole/templates/pre-install-job.yaml | 32 - .../charts/pihole/templates/secret.yaml | 8 - .../ix-dev/charts/pihole/to_keep_versions.md | 4 + .../charts/pihole/to_keep_versions.yaml | 1 + library/ix-dev/charts/pihole/values.yaml | 32 + 26 files changed, 787 insertions(+), 422 deletions(-) create mode 100644 library/ix-dev/charts/pihole/charts/common-1.2.9.tgz delete mode 100644 library/ix-dev/charts/pihole/charts/common-2304.0.1.tgz create mode 100644 library/ix-dev/charts/pihole/ci/basic-values.yaml delete mode 100644 library/ix-dev/charts/pihole/ci/hostnet-values.yaml create mode 100644 library/ix-dev/charts/pihole/ci/nodhcp-values.yaml delete mode 100644 library/ix-dev/charts/pihole/ci/test-values.yaml create mode 100755 library/ix-dev/charts/pihole/migrations/migrate create mode 100644 library/ix-dev/charts/pihole/templates/NOTES.txt create mode 100644 library/ix-dev/charts/pihole/templates/_migration.tpl create mode 100644 library/ix-dev/charts/pihole/templates/_persistence.tpl create mode 100644 library/ix-dev/charts/pihole/templates/_pihole.tpl create mode 100644 library/ix-dev/charts/pihole/templates/_portal.tpl create mode 100644 library/ix-dev/charts/pihole/templates/_service.tpl create mode 100644 library/ix-dev/charts/pihole/templates/common.yaml delete mode 100644 library/ix-dev/charts/pihole/templates/deployment.yaml delete mode 100644 library/ix-dev/charts/pihole/templates/pre-install-job.yaml delete mode 100644 library/ix-dev/charts/pihole/templates/secret.yaml create mode 100644 library/ix-dev/charts/pihole/to_keep_versions.md create mode 100644 library/ix-dev/charts/pihole/to_keep_versions.yaml diff --git a/library/ix-dev/charts/pihole/Chart.lock b/library/ix-dev/charts/pihole/Chart.lock index cad56646fb..689110964a 100644 --- a/library/ix-dev/charts/pihole/Chart.lock +++ b/library/ix-dev/charts/pihole/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: common - repository: file://../../../common/2304.0.1 - version: 2304.0.1 -digest: sha256:1ed155c6760e1166e2cb75b52bc5e81c6bdf0252c16ff5ede001157077c41670 -generated: "2023-04-24T13:41:05.966111097+03:00" + repository: file://../../../common + version: 1.2.9 +digest: sha256:af1a9a1f87e3e48453c9f25f909f5ebcd7fa6e25162b7b425448ba752bcdbc5c +generated: "2024-02-08T16:30:19.001779605+02:00" diff --git a/library/ix-dev/charts/pihole/Chart.yaml b/library/ix-dev/charts/pihole/Chart.yaml index cc6f5f4026..fa98d6969c 100644 --- a/library/ix-dev/charts/pihole/Chart.yaml +++ b/library/ix-dev/charts/pihole/Chart.yaml @@ -3,7 +3,7 @@ description: DNS and Ad-filtering for your network. annotations: title: Pi-hole type: application -version: 1.0.25 +version: 2.0.0 apiVersion: v2 appVersion: 2023.11.0 kubeVersion: '>=1.16.0-0' @@ -13,8 +13,8 @@ maintainers: email: dev@ixsystems.com dependencies: - name: common - repository: file://../../../common/2304.0.1 - version: 2304.0.1 + repository: file://../../../common + version: 1.2.9 home: https://pi-hole.net/ icon: https://media.sys.truenas.net/apps/pihole/icons/icon.png sources: diff --git a/library/ix-dev/charts/pihole/README.md b/library/ix-dev/charts/pihole/README.md index 98d1bcdb3b..7bd26b05b7 100644 --- a/library/ix-dev/charts/pihole/README.md +++ b/library/ix-dev/charts/pihole/README.md @@ -1,3 +1,3 @@ -# Pihole +# Pi-hole -DNS and Ad-filtering for your network. +[Pi-hole](https://pi-hole.net/) is a black hole for Internet advertisements diff --git a/library/ix-dev/charts/pihole/app-readme.md b/library/ix-dev/charts/pihole/app-readme.md index 98d1bcdb3b..7bd26b05b7 100644 --- a/library/ix-dev/charts/pihole/app-readme.md +++ b/library/ix-dev/charts/pihole/app-readme.md @@ -1,3 +1,3 @@ -# Pihole +# Pi-hole -DNS and Ad-filtering for your network. +[Pi-hole](https://pi-hole.net/) is a black hole for Internet advertisements diff --git a/library/ix-dev/charts/pihole/charts/common-1.2.9.tgz b/library/ix-dev/charts/pihole/charts/common-1.2.9.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f177ca94969ee629db52f9b9d95db0352f04866b GIT binary patch literal 63211 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 CgDc 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 M{~k9>5dew+00L>?;{X5v diff --git a/library/ix-dev/charts/pihole/ci/basic-values.yaml b/library/ix-dev/charts/pihole/ci/basic-values.yaml new file mode 100644 index 0000000000..d800ff9dd4 --- /dev/null +++ b/library/ix-dev/charts/pihole/ci/basic-values.yaml @@ -0,0 +1,16 @@ +piholeConfig: + webPassword: somepassword + +piholeNetwork: + webPort: 32000 + dhcp: + enabled: true + start: '192.168.1.1' + end: '192.168.1.2' + gateway: '192.168.1.0' + +piholeStorage: + config: + type: pvc + dnsmasq: + type: pvc diff --git a/library/ix-dev/charts/pihole/ci/hostnet-values.yaml b/library/ix-dev/charts/pihole/ci/hostnet-values.yaml deleted file mode 100644 index 110f705716..0000000000 --- a/library/ix-dev/charts/pihole/ci/hostnet-values.yaml +++ /dev/null @@ -1,22 +0,0 @@ -appVolumeMounts: - config: - emptyDir: true - mountPath: /etc/pihole - dnsmasq: - emptyDir: true - mountPath: /etc/dnsmasq.d -dhcp: true -dhcp_start: 192.168.10.2 -dhcp_end: 192.168.10.254 -dhcp_gateway: 192.168.10.1 -dnsConfig: - options: [] -emptyDirVolumes: true -environmentVariables: [] -extraAppVolumeMounts: [] -hostNetwork: true -ownerGID: 568 -ownerUID: 568 -password: admin123 -timezone: America/Los_Angeles -web_port: 32000 diff --git a/library/ix-dev/charts/pihole/ci/nodhcp-values.yaml b/library/ix-dev/charts/pihole/ci/nodhcp-values.yaml new file mode 100644 index 0000000000..843efc4520 --- /dev/null +++ b/library/ix-dev/charts/pihole/ci/nodhcp-values.yaml @@ -0,0 +1,13 @@ +piholeConfig: + webPassword: somepassword + +piholeNetwork: + webPort: 32000 + dhcp: + enabled: false + +piholeStorage: + config: + type: pvc + dnsmasq: + type: pvc diff --git a/library/ix-dev/charts/pihole/ci/test-values.yaml b/library/ix-dev/charts/pihole/ci/test-values.yaml deleted file mode 100644 index 941105ba4d..0000000000 --- a/library/ix-dev/charts/pihole/ci/test-values.yaml +++ /dev/null @@ -1,20 +0,0 @@ -appVolumeMounts: - config: - emptyDir: true - mountPath: /etc/pihole - dnsmasq: - emptyDir: true - mountPath: /etc/dnsmasq.d -dhcp: true -dnsConfig: - options: [] -dns_tcp_port: 32001 -dns_udp_port: 32002 -emptyDirVolumes: true -environmentVariables: [] -extraAppVolumeMounts: [] -ownerGID: 568 -ownerUID: 568 -password: admin123 -timezone: America/Los_Angeles -web_port: 32000 diff --git a/library/ix-dev/charts/pihole/metadata.yaml b/library/ix-dev/charts/pihole/metadata.yaml index 6e9a68e0d1..f24c69667b 100644 --- a/library/ix-dev/charts/pihole/metadata.yaml +++ b/library/ix-dev/charts/pihole/metadata.yaml @@ -9,30 +9,16 @@ capabilities: description: Pi-hole is able to chown files. - name: FOWNER description: Pi-hole is able to bypass permission checks for it's sub-processes. - - name: SYS_CHROOT - description: Pi-hole is able to use chroot. - - name: MKNOD - description: Pi-hole is able to create device nodes. - name: DAC_OVERRIDE description: Pi-hole is able to bypass permission checks. - - name: FSETID - description: Pi-hole is able to set file capabilities. - - name: KILL - description: Pi-hole is able to kill processes. - name: SETGID description: Pi-hole is able to set group ID for it's sub-processes. - name: SETUID description: Pi-hole is able to set user ID for it's sub-processes. - - name: SETPCAP - description: Pi-hole is able to set process capabilities. - - name: NET_BIND_SERVICE - description: Pi-hole is able to bind to privileged ports. - name: SETFCAP description: Pi-hole is able to set file capabilities. - - name: NET_RAW - description: Pi-hole is able to use raw sockets. - name: NET_ADMIN description: Pi-hole is able to perform various network-related operations. - - name: AUDIT_WRITE - description: Pi-hole is able to write to audit log. + - name: KILL + description: Pi-hole is able to kill processes. hostMounts: [] diff --git a/library/ix-dev/charts/pihole/migrations/migrate b/library/ix-dev/charts/pihole/migrations/migrate new file mode 100755 index 0000000000..24afaf26a4 --- /dev/null +++ b/library/ix-dev/charts/pihole/migrations/migrate @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +import json +import os +import sys + +def migrate_volume(volume): + return { + 'type': 'hostPath', + 'hostPathConfig': { + 'hostPath': volume['hostPath'] + }, + } if volume.get('hostPathEnabled', False) else { + 'type': 'ixVolume', + 'ixVolumeConfig': { + 'datasetName': volume['datasetName'], + }, + } + +def migrate_common_lib(values): + delete_keys = [ + 'enableResourceLimits', 'memLimit', 'cpuLimit', 'dnsConfig', + 'web_port', 'environmentVariables', 'timezone', 'password', + 'extraAppVolumeMounts', 'appVolumeMounts', 'dhcp', 'dhcp_start', + 'dhcp_end', 'dhcp_gateway', 'ownerUID', 'ownerGID', + ] + + values.update({ + # Migrate Network + 'piholeNetwork': { + 'webPort': values['web_port'], + 'dhcp': { + 'enabled': values['dhcp'], + 'start': values.get('dhcp_start', ''), + 'end': values.get('dhcp_end', ''), + 'gateway': values.get('dhcp_gateway', ''), + } + }, + # Migrate Resources + 'resources': { + 'limits': { + 'cpu': values.get('cpuLimit', '4000m'), + 'memory': values.get('memLimit', '8Gi'), + } + }, + # Migrate DNS + 'podOptions': { + 'dnsConfig': { + 'options': [ + {'name': opt['name'], 'value': opt['value']} + for opt in values.get('dnsConfig', {}).get('options', []) + ] + } + }, + # Migrate Config + 'TZ': values['timezone'], + 'piholeConfig': { + 'webPassword': values['password'], + 'additionalEnvs': values.get('environmentVariables', []), + }, + # Migrate Storage + 'piholeStorage': { + 'config': migrate_volume(values['appVolumeMounts']['config']), + 'cache': migrate_volume(values['appVolumeMounts']['dnsmasq']), + 'additionalStorages': [ + { + 'type': 'hostPath', + 'hostPathConfig': {'hostPath': e['hostPath']}, + 'mountPath': e['mountPath'], + 'readOnly': e.get('readOnly', False), + } + for e in values.get('extraAppVolumeMounts', []) + ], + }, + }) + + for k in delete_keys: + values.pop(k, None) + + return values + +def migrate(values): + # If this missing, we have already migrated + if not 'appVolumeMounts' in values.keys(): + return values + + return migrate_common_lib(values) + + +if __name__ == '__main__': + if len(sys.argv) != 2: + exit(1) + + if os.path.exists(sys.argv[1]): + with open(sys.argv[1], 'r') as f: + print(json.dumps(migrate(json.loads(f.read())))) diff --git a/library/ix-dev/charts/pihole/questions.yaml b/library/ix-dev/charts/pihole/questions.yaml index 190c73c38c..ed7f35e2b5 100644 --- a/library/ix-dev/charts/pihole/questions.yaml +++ b/library/ix-dev/charts/pihole/questions.yaml @@ -1,277 +1,499 @@ groups: - - name: "Configuration" - description: "Pihole application configuration" - - name: "Storage" - description: "Configure storage for pihole" - - name: "Networking" - description: "Networking Configuration for pihole" - - name: "Advanced DNS Settings" - description: "Configure DNS settings" - - name: "Resource Limits" - description: "Set CPU/memory limits for Kubernetes Pod" - + - name: Pi-Hole Configuration + description: Configure Pi-Hole + - name: Advanced Pod Configuration + description: Configure Advanced Pod Options for Pi-Hole + - name: Network Configuration + description: Configure Network for Pi-Hole + - name: Storage Configuration + description: Configure Storage for Pi-Hole + - name: Resources Configuration + description: Configure Resources for Pi-Hole portals: web_portal: protocols: - - "http" + - "$kubernetes-resource_configmap_portal_protocol" host: - - "$node_ip" + - "$kubernetes-resource_configmap_portal_host" ports: - - "$variable-web_port" - path: "/admin/" + - "$kubernetes-resource_configmap_portal_port" + path: "$kubernetes-resource_configmap_portal_path" questions: - - variable: web_port - label: "Web Port for pihole" - group: Networking + - variable: TZ + group: Pi-Hole Configuration + label: Timezone schema: - type: int - min: 8000 - max: 65535 - default: 20720 + type: string + default: Etc/UTC required: true + $ref: + - definitions/timezone - - variable: dhcp - label: "Enable DHCP" - group: "Networking" - schema: - type: boolean - default: false - show_subquestions_if: true - subquestions: - - variable: dhcp_start - label: "DHCP Start Address" - group: Networking - schema: - type: ipaddr - cidr: false - required: true - - variable: dhcp_end - label: "DHCP End Address" - group: Networking - schema: - type: ipaddr - cidr: false - required: true - - variable: dhcp_gateway - label: "Gateway" - group: Networking - schema: - type: ipaddr - cidr: false - required: true - - - variable: dnsConfig - label: "DNS Configuration" - group: "Advanced DNS Settings" + - variable: piholeConfig + label: "" + group: Pi-Hole Configuration schema: type: dict attrs: - - variable: options - label: "DNS Options" + - variable: webPassword + label: Web Password + description: The password for the Pi-Hole Web UI. + schema: + type: string + required: true + private: true + - variable: additionalEnvs + label: Additional Environment Variables + description: Configure additional environment variables for Pi-Hole. schema: type: list + default: [] items: - - variable: optionsEntry - label: "Option Entry Configuration" + - variable: env + label: Environment Variable schema: type: dict attrs: - variable: name - label: "Option Name" + label: Name schema: type: string required: true - variable: value - label: "Option Value" + label: Value schema: type: string required: true - - variable: ownerUID - label: "Storage User ID" - description: "User ID of the dnsmasq volume being used (application will chown the volume path with specified UID)" - group: Configuration + - variable: podOptions + label: "" + group: Advanced Pod Configuration schema: - type: int - default: 568 - min: 1 - max: 65535 - - - variable: ownerGID - label: "Storage Group ID" - description: "Group ID of the dnsmasq volume being used (application will chown the volume path with specified GID)" - group: Configuration - schema: - type: int - default: 568 - min: 1 - max: 65535 - - - variable: password - label: "Admin password" - group: "Configuration" - schema: - type: string - private: true - required: true - empty: false - immutable: true - - - variable: timezone - label: "Configure timezone" - group: "Configuration" - description: "Configure timezone for pihole" - schema: - type: string - $ref: - - "definitions/timezone" - - - variable: environmentVariables - label: "Pihole environment" - group: "Configuration" - schema: - type: list - default: [] - items: - - variable: environmentVariable - label: "Environment Variable" + type: dict + attrs: + - variable: dnsConfig + label: Advanced DNS Configuration schema: type: dict attrs: - - variable: name - label: "Name" + - variable: options + label: DNS Options schema: - type: string - - variable: value - label: "Value" - schema: - type: string + type: list + items: + - variable: optionsEntry + label: DNS Option Entry + schema: + type: dict + attrs: + - variable: name + label: Option Name + schema: + type: string + required: true + - variable: value + label: Option Value + schema: + type: string + required: true - - variable: appVolumeMounts - label: "Pihole Storage" - group: "Storage" + - variable: piholeNetwork + label: "" + group: Network Configuration + schema: + type: dict + attrs: + - variable: webPort + label: Web Port + description: The port for the Pi-Hole Web UI. + schema: + type: int + default: 20720 + min: 9000 + max: 65535 + required: true + - variable: dhcp + label: DHCP Configuration + schema: + type: dict + attrs: + - variable: enabled + label: Enable DHCP + description: Enable DHCP for Pi-Hole. + schema: + type: boolean + default: false + - variable: start + label: DHCP Start + description: The start of the DHCP range. + schema: + type: ipaddr + cidr: false + show_if: [["enabled", "=", true]] + required: true + - variable: end + label: DHCP End + description: The end of the DHCP range. + schema: + type: ipaddr + cidr: false + show_if: [["enabled", "=", true]] + required: true + - variable: gateway + label: DHCP Gateway + description: The gateway for the DHCP range. + schema: + type: ipaddr + cidr: false + show_if: [["enabled", "=", true]] + required: true + + - variable: piholeStorage + label: "" + group: Storage Configuration schema: type: dict attrs: - variable: config - label: "Configuration Volume" + label: Pi-Hole Config Storage + description: The path to store Pi-Hole Configuration. schema: type: dict attrs: - - variable: datasetName - label: "Configuration Volume Dataset Name" + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. schema: type: string - hidden: true + required: true + immutable: true + default: "ixVolume" + enum: + - value: "hostPath" + description: Host Path (Path that already exists on the system) + - value: "ixVolume" + description: ixVolume (Dataset created automatically by the system) + - variable: ixVolumeConfig + label: ixVolume Configuration + description: The configuration for the ixVolume dataset. + schema: + type: dict + show_if: [["type", "=", "ixVolume"]] $ref: - "normalize/ixVolume" - show_if: [["hostPathEnabled", "=", false]] - default: "ix-pihole_config" - editable: false - - variable: mountPath - label: "Configuration Mount Path" - description: "Path where the volume will be mounted inside the pod" + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: datasetName + label: Dataset Name + description: The name of the dataset to use for storage. + schema: + type: string + required: true + immutable: true + hidden: true + default: "config" + - variable: aclEntries + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + - variable: hostPathConfig + label: Host Path Config schema: - type: path - hidden: true - editable: true - default: "/etc/pihole" - - variable: hostPathEnabled - label: "Enable Custom Host Path for Pihole Configuration Volume" - schema: - type: boolean - default: false - show_subquestions_if: true - subquestions: + type: dict + show_if: [["type", "=", "hostPath"]] + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: acl + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + $ref: + - "normalize/acl" - variable: hostPath - label: "Host Path for Pihole Configuration Volume" + label: Host Path + description: The host path to use for storage. schema: type: hostpath + show_if: [["aclEnable", "=", false]] required: true - variable: dnsmasq - label: "DNSMASQ Volume for pihole" + label: Pi-Hole DNSmasq Storage + description: The path to store Pi-Hole DNSmasq. schema: type: dict attrs: - - variable: datasetName - label: "DNSMASQ Volume Dataset Name" + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. schema: type: string - hidden: true + required: true + immutable: true + default: "ixVolume" + enum: + - value: "hostPath" + description: Host Path (Path that already exists on the system) + - value: "ixVolume" + description: ixVolume (Dataset created automatically by the system) + - variable: ixVolumeConfig + label: ixVolume Configuration + description: The configuration for the ixVolume dataset. + schema: + type: dict + show_if: [["type", "=", "ixVolume"]] $ref: - "normalize/ixVolume" - show_if: [["hostPathEnabled", "=", false]] - default: "ix-pihole_dnsmasq" - editable: false - - variable: mountPath - label: "DNSMASQ Mount Path" - description: "Path where the volume will be mounted inside the pod" + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: datasetName + label: Dataset Name + description: The name of the dataset to use for storage. + schema: + type: string + required: true + immutable: true + hidden: true + default: "dnsmasq" + - variable: aclEntries + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + - variable: hostPathConfig + label: Host Path Config schema: - type: path - hidden: true - editable: true - default: "/etc/dnsmasq.d" - - variable: hostPathEnabled - label: "Enable Custom Host Path for Pihole DNSMASQ Volume" - schema: - type: boolean - default: false - show_subquestions_if: true - subquestions: + type: dict + show_if: [["type", "=", "hostPath"]] + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: acl + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + $ref: + - "normalize/acl" - variable: hostPath - label: "Host Path for Pihole DNSMASQ Volume" + label: Host Path + description: The host path to use for storage. schema: type: hostpath + show_if: [["aclEnable", "=", false]] required: true - - variable: extraAppVolumeMounts - label: "Extra Host Path Volumes" - group: "Storage" + - variable: additionalStorages + label: Additional Storage + description: Additional storage for Pi-Hole. + schema: + type: list + default: [] + items: + - variable: storageEntry + label: Storage Entry + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system.
+ SMB Share: Is a SMB share that is mounted to a persistent volume claim. + schema: + type: string + required: true + default: "ixVolume" + immutable: true + enum: + - value: "hostPath" + description: Host Path (Path that already exists on the system) + - value: "ixVolume" + description: ixVolume (Dataset created automatically by the system) + - value: "smb-pv-pvc" + description: SMB Share (Mounts a persistent volume claim to a SMB share) + - variable: readOnly + label: Read Only + description: Mount the volume as read only. + schema: + type: boolean + default: false + - variable: mountPath + label: Mount Path + description: The path inside the container to mount the storage. + schema: + type: path + required: true + - variable: hostPathConfig + label: Host Path Config + schema: + type: dict + show_if: [["type", "=", "hostPath"]] + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: acl + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + $ref: + - "normalize/acl" + - variable: hostPath + label: Host Path + description: The host path to use for storage. + schema: + type: hostpath + show_if: [["aclEnable", "=", false]] + required: true + - variable: ixVolumeConfig + label: ixVolume Configuration + description: The configuration for the ixVolume dataset. + schema: + type: dict + show_if: [["type", "=", "ixVolume"]] + $ref: + - "normalize/ixVolume" + attrs: + - variable: aclEnable + label: Enable ACL + description: Enable ACL for the dataset. + schema: + type: boolean + default: false + - variable: datasetName + label: Dataset Name + description: The name of the dataset to use for storage. + schema: + type: string + required: true + immutable: true + default: "storage_entry" + - variable: aclEntries + label: ACL Configuration + schema: + type: dict + show_if: [["aclEnable", "=", true]] + attrs: [] + - variable: smbConfig + label: SMB Share Configuration + description: The configuration for the SMB Share. + schema: + type: dict + show_if: [["type", "=", "smb-pv-pvc"]] + attrs: + - variable: server + label: Server + description: The server for the SMB share. + schema: + type: string + required: true + - variable: share + label: Share + description: The share name for the SMB share. + schema: + type: string + required: true + - variable: domain + label: Domain (Optional) + description: The domain for the SMB share. + schema: + type: string + - variable: username + label: Username + description: The username for the SMB share. + schema: + type: string + required: true + - variable: password + label: Password + description: The password for the SMB share. + schema: + type: string + required: true + private: true + - variable: size + label: Size (in Gi) + description: The size of the volume quota. + schema: + type: int + required: true + min: 1 + default: 1 + + - variable: resources + group: Resources Configuration + label: "" schema: - type: list - items: - - variable: extraAppVolume - label: "Host Path Volume" - description: "Add an extra host path volume for Pihole application" + type: dict + attrs: + - variable: limits + label: Limits schema: type: dict attrs: - - variable: mountPath - label: "Mount Path in Pod" - description: "Path where the volume will be mounted inside the pod" + - variable: cpu + label: CPU + description: CPU limit for Pi-Hole. schema: - type: path + type: string + max_length: 6 + valid_chars: '^(0\.[1-9]|[1-9][0-9]*)(\.[0-9]|m?)$' + valid_chars_error: | + Valid CPU limit formats are
+ - Plain Integer - eg. 1
+ - Float - eg. 0.5
+ - Milicpu - eg. 500m + default: "4000m" required: true - - variable: hostPath - label: "Host Path" - description: "Host path" + - variable: memory + label: Memory + description: Memory limit for Pi-Hole. schema: - type: hostpath + type: string + max_length: 12 + valid_chars: '^[1-9][0-9]*([EPTGMK]i?|e[0-9]+)?$' + valid_chars_error: | + Valid Memory limit formats are
+ - Suffixed with E/P/T/G/M/K - eg. 1G
+ - Suffixed with Ei/Pi/Ti/Gi/Mi/Ki - eg. 1Gi
+ - Plain Integer in bytes - eg. 1024
+ - Exponent - eg. 134e6 + default: "8Gi" required: true - - - variable: enableResourceLimits - label: "Enable Pod resource limits" - group: "Resource Limits" - schema: - type: boolean - default: false - - variable: cpuLimit - label: "CPU Limit" - description: "CPU resource limit allow plain integer values with suffix m(milli) e.g 1000m, 100." - group: "Resource Limits" - schema: - type: string - show_if: [["enableResourceLimits", "=", true]] - valid_chars: "^\\d+(?:\\.\\d+(?!.*m$)|m?$)" - default: "4000m" - - variable: memLimit - label: "Memory Limit" - group: "Resource Limits" - description: "Memory limits is specified by number of bytes. Followed by quantity suffix like E,P,T,G,M,k and Ei,Pi,Ti,Mi,Gi,Ki can also be used. e.g 129e6, 129M, 128974848000m, 123Mi" - schema: - type: string - show_if: [["enableResourceLimits", "=", true]] - valid_chars: "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" - default: "8Gi" diff --git a/library/ix-dev/charts/pihole/templates/NOTES.txt b/library/ix-dev/charts/pihole/templates/NOTES.txt new file mode 100644 index 0000000000..ba4e01146c --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/NOTES.txt @@ -0,0 +1 @@ +{{ include "ix.v1.common.lib.chart.notes" $ }} diff --git a/library/ix-dev/charts/pihole/templates/_migration.tpl b/library/ix-dev/charts/pihole/templates/_migration.tpl new file mode 100644 index 0000000000..c5cb9b435e --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/_migration.tpl @@ -0,0 +1,35 @@ +{{- define "pihole.get-versions" -}} + {{- $oldChartVersion := "" -}} + {{- $newChartVersion := "" -}} + + {{/* Safely access the context, so it wont block CI */}} + {{- if hasKey .Values.global "ixChartContext" -}} + {{- if .Values.global.ixChartContext.upgradeMetadata -}} + + {{- $oldChartVersion = .Values.global.ixChartContext.upgradeMetadata.oldChartVersion -}} + {{- $newChartVersion = .Values.global.ixChartContext.upgradeMetadata.newChartVersion -}} + {{- if and (not $oldChartVersion) (not $newChartVersion) -}} + {{- fail "Upgrade Metadata is missing. Cannot proceed" -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- toYaml (dict "old" $oldChartVersion "new" $newChartVersion) -}} +{{- end -}} + +{{- define "pihole.migration" -}} + {{- $versions := (fromYaml (include "pihole.get-versions" $)) -}} + {{- if and $versions.old $versions.new -}} + {{- $oldV := semver $versions.old -}} + {{- $newV := semver $versions.new -}} + + {{/* If new is v2.x.x */}} + {{- if eq ($newV.Major | int) 2 -}} + {{/* And old is v1.x.x, but lower than .25 */}} + {{- if and (eq $oldV.Major 1) (lt ($oldV.Patch | int) 25) -}} + {{/* Block the upgrade */}} + {{- fail "Migration to 2.x.x is only allowed from 1.0.25 or higher" -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/library/ix-dev/charts/pihole/templates/_persistence.tpl b/library/ix-dev/charts/pihole/templates/_persistence.tpl new file mode 100644 index 0000000000..7b939b8492 --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/_persistence.tpl @@ -0,0 +1,33 @@ +{{- define "pihole.persistence" -}} +persistence: + config: + enabled: true + {{- include "ix.v1.common.app.storageOptions" (dict "storage" .Values.piholeStorage.config) | nindent 4 }} + targetSelector: + pihole: + pihole: + mountPath: /etc/pihole + dnsmasq: + enabled: true + {{- include "ix.v1.common.app.storageOptions" (dict "storage" .Values.piholeStorage.dnsmasq) | nindent 4 }} + targetSelector: + pihole: + pihole: + mountPath: /etc/dnsmasq.d + tmp: + enabled: true + type: emptyDir + targetSelector: + pihole: + pihole: + mountPath: /tmp + {{- range $idx, $storage := .Values.piholeStorage.additionalStorages }} + {{ printf "pihole-%v:" (int $idx) }} + enabled: true + {{- include "ix.v1.common.app.storageOptions" (dict "storage" $storage) | nindent 4 }} + targetSelector: + pihole: + pihole: + mountPath: {{ $storage.mountPath }} + {{- end }} +{{- end -}} diff --git a/library/ix-dev/charts/pihole/templates/_pihole.tpl b/library/ix-dev/charts/pihole/templates/_pihole.tpl new file mode 100644 index 0000000000..b46fafa8ae --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/_pihole.tpl @@ -0,0 +1,61 @@ +{{- define "pihole.workload" -}} +workload: + pihole: + enabled: true + primary: true + type: Deployment + podSpec: + hostNetwork: true + containers: + pihole: + enabled: true + primary: true + imageSelector: image + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + capabilities: + add: + - NET_ADMIN + - CHOWN + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + - SETFCAP + - KILL + env: + WEB_PORT: {{ .Values.piholeNetwork.webPort }} + WEBPASSWORD: {{ .Values.piholeConfig.webPassword }} + {{- if .Values.piholeNetwork.dhcp.enabled }} + DHCP_ACTIVE: "true" + DHCP_START: {{ .Values.piholeNetwork.dhcp.start }} + DHCP_END: {{ .Values.piholeNetwork.dhcp.end }} + DHCP_ROUTER: {{ .Values.piholeNetwork.dhcp.gateway }} + {{- end }} + {{ with .Values.piholeConfig.additionalEnvs }} + envList: + {{ range $env := . }} + - name: {{ $env.name }} + value: {{ $env.value }} + {{ end }} + {{ end }} + probes: + liveness: + enabled: true + type: http + path: /admin/login.php + port: {{ .Values.piholeNetwork.webPort }} + readiness: + enabled: true + type: http + path: /admin/login.php + port: {{ .Values.piholeNetwork.webPort }} + startup: + enabled: true + type: http + path: /admin/login.php + port: {{ .Values.piholeNetwork.webPort }} +{{- end -}} diff --git a/library/ix-dev/charts/pihole/templates/_portal.tpl b/library/ix-dev/charts/pihole/templates/_portal.tpl new file mode 100644 index 0000000000..3c0fa210ef --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/_portal.tpl @@ -0,0 +1,12 @@ +{{- define "pihole.portal" -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: portal +data: + port: {{ .Values.piholeNetwork.webPort | quote }} + path: "/admin/" + protocol: "http" + host: $node_ip +{{- end -}} diff --git a/library/ix-dev/charts/pihole/templates/_service.tpl b/library/ix-dev/charts/pihole/templates/_service.tpl new file mode 100644 index 0000000000..9ba8c80aba --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/_service.tpl @@ -0,0 +1,33 @@ +{{- define "pihole.service" -}} +service: + pihole: + enabled: true + primary: true + type: ClusterIP + targetSelector: pihole + ports: + webui: + enabled: true + primary: true + port: {{ .Values.piholeNetwork.webPort }} + targetSelector: pihole + dns-udp: + enabled: true + port: 53 + targetPort: 53 + protocol: udp + targetSelector: pihole + dns-tcp: + enabled: true + port: 53 + targetPort: 53 + targetSelector: pihole + {{- if .Values.piholeNetwork.dhcpEnabled }} + dhcp: + enabled: true + port: 67 + targetPort: 67 + protocol: udp + targetSelector: pihole + {{- end }} +{{- end -}} diff --git a/library/ix-dev/charts/pihole/templates/common.yaml b/library/ix-dev/charts/pihole/templates/common.yaml new file mode 100644 index 0000000000..7c0e18bb59 --- /dev/null +++ b/library/ix-dev/charts/pihole/templates/common.yaml @@ -0,0 +1,13 @@ +{{- include "ix.v1.common.loader.init" . -}} + +{{- include "pihole.migration" $ -}} + +{{/* Merge the templates with Values */}} +{{- $_ := mustMergeOverwrite .Values (include "pihole.workload" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "pihole.service" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "pihole.persistence" $ | fromYaml) -}} + +{{/* Create the configmap for portal manually*/}} +{{- include "pihole.portal" $ -}} + +{{- include "ix.v1.common.loader.apply" . -}} diff --git a/library/ix-dev/charts/pihole/templates/deployment.yaml b/library/ix-dev/charts/pihole/templates/deployment.yaml deleted file mode 100644 index 09635f83d8..0000000000 --- a/library/ix-dev/charts/pihole/templates/deployment.yaml +++ /dev/null @@ -1,110 +0,0 @@ -{{ include "common.storage.hostPathValidate" .Values }} -apiVersion: {{ template "common.capabilities.deployment.apiVersion" . }} -kind: Deployment -metadata: - name: {{ template "common.names.fullname" . }}-pihole - labels: - app: {{ template "common.names.name" . }} - chart: {{ template "common.names.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - annotations: - rollme: {{ randAlphaNum 5 | quote }} -spec: - replicas: {{ (default 1 .Values.replicas) }} - strategy: - type: "Recreate" - selector: - matchLabels: - app: {{ template "common.names.name" . }} - release: {{ .Release.Name }} - template: - metadata: - name: {{ template "common.names.fullname" . }} - labels: - app: {{ template "common.names.name" . }} - release: {{ .Release.Name }} - {{- include "common.labels.selectorLabels" . | nindent 8 }} - annotations: {{ include "common.annotations" . | nindent 8 }} - spec: - {{/* - Host network is pretty much a requirement for apps like this. - Because NodePort can't bind ports like 53(DNS) or 67(DHCP) - and the majority of devices do not have option to change the port. - */}} - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - containers: - - name: {{ .Chart.Name }} - {{ include "common.resources.limitation" . | nindent 10 }} - {{ include "common.containers.imageConfig" .Values.image | nindent 10 }} - volumeMounts: {{ include "common.storage.configureAppVolumeMountsInContainer" .Values | nindent 12 }} - {{ range $index, $hostPathConfiguration := .Values.extraAppVolumeMounts }} - - name: extrappvolume-{{ $index }} - mountPath: {{ $hostPathConfiguration.mountPath }} - {{ end }} - securityContext: - capabilities: - {{/* This is needed to be able to bind 53(DNS) and 67(DHCP) ports */}} - add: ["NET_ADMIN"] - ports: - - name: web - containerPort: {{ .Values.web_port }} - - name: dns-tcp - containerPort: 53 - protocol: TCP - - name: dns-udp - containerPort: 53 - protocol: UDP - {{ if .Values.dhcp }} - - name: dhcp - containerPort: 67 - protocol: UDP - {{ end }} - readinessProbe: - httpGet: - path: /admin/login.php - port: {{ .Values.web_port }} - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 5 - successThreshold: 2 - livenessProbe: - httpGet: - path: /admin/login.php - port: {{ .Values.web_port }} - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 5 - successThreshold: 1 - startupProbe: - httpGet: - path: /admin/login.php - port: {{ .Values.web_port }} - initialDelaySeconds: 10 - periodSeconds: 5 - timeoutSeconds: 2 - failureThreshold: 60 - successThreshold: 1 - env: - {{ $secretName := (include "common.names.fullname" .) }} - {{ $envList := (default list .Values.environmentVariables) }} - {{ $envList = mustAppend $envList (dict "name" "WEBPASSWORD" "valueFromSecret" true "secretName" $secretName "secretKey" "password") }} - {{ $envList = mustAppend $envList (dict "name" "TZ" "value" (printf "%s" .Values.timezone)) }} - {{ $envList = mustAppend $envList (dict "name" "WEB_PORT" "value" .Values.web_port) }} - {{ if .Values.dhcp }} - {{ $envList = mustAppend $envList (dict "name" "DHCP_ACTIVE" "value" "true") }} - {{ $envList = mustAppend $envList (dict "name" "DHCP_START" "value" .Values.dhcp_start) }} - {{ $envList = mustAppend $envList (dict "name" "DHCP_END" "value" .Values.dhcp_end) }} - {{ $envList = mustAppend $envList (dict "name" "DHCP_ROUTER" "value" .Values.dhcp_gateway) }} - {{ end }} - {{ include "common.containers.environmentVariables" (dict "environmentVariables" $envList) | nindent 12 }} -{{ include "common.networking.dnsConfiguration" .Values | nindent 6 }} - volumes: {{ include "common.storage.configureAppVolumes" .Values | nindent 8 }} - {{ range $index, $hostPathConfiguration := .Values.extraAppVolumeMounts }} - - name: extrappvolume-{{ $index }} - hostPath: - path: {{ $hostPathConfiguration.hostPath }} - {{ end }} diff --git a/library/ix-dev/charts/pihole/templates/pre-install-job.yaml b/library/ix-dev/charts/pihole/templates/pre-install-job.yaml deleted file mode 100644 index 8ef9ea0f26..0000000000 --- a/library/ix-dev/charts/pihole/templates/pre-install-job.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: "{{ template "common.names.fullname" . }}-preinstall-job" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} - helm.sh/chart: {{ template "common.names.chart" . }} - annotations: - "helm.sh/hook": pre-install - "helm.sh/hook-delete-policy": hook-succeeded -spec: - template: - metadata: - name: "{{ template "common.names.fullname" . }}-preinstall-hook" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - helm.sh/chart: {{ template "common.names.chart" . }} - spec: - restartPolicy: Never - containers: - - name: pre-install-job - image: "alpine:latest" - command: - - "chown" - - "-R" - - "{{ .Values.ownerUID }}:{{ .Values.ownerGID }}" - - "{{ .Values.appVolumeMounts.dnsmasq.mountPath }}" - volumeMounts: {{ include "common.storage.configureAppVolumeMountsInContainer" .Values | nindent 12 }} - volumes: {{ include "common.storage.configureAppVolumes" .Values | nindent 8 }} diff --git a/library/ix-dev/charts/pihole/templates/secret.yaml b/library/ix-dev/charts/pihole/templates/secret.yaml deleted file mode 100644 index e531b6015e..0000000000 --- a/library/ix-dev/charts/pihole/templates/secret.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "common.names.fullname" . }} - labels: {{ include "common.labels" . | nindent 4 }} -type: Opaque -data: - password: {{ .Values.password | b64enc | quote }} diff --git a/library/ix-dev/charts/pihole/to_keep_versions.md b/library/ix-dev/charts/pihole/to_keep_versions.md new file mode 100644 index 0000000000..d8831326d9 --- /dev/null +++ b/library/ix-dev/charts/pihole/to_keep_versions.md @@ -0,0 +1,4 @@ +# 1.0.25 + +This version is kept because it contains a fix that is needed for migration to v2.x.x +It should be safe to remove few months after v2.x.x is released. diff --git a/library/ix-dev/charts/pihole/to_keep_versions.yaml b/library/ix-dev/charts/pihole/to_keep_versions.yaml new file mode 100644 index 0000000000..6d1b2f844c --- /dev/null +++ b/library/ix-dev/charts/pihole/to_keep_versions.yaml @@ -0,0 +1 @@ +- 1.0.25 diff --git a/library/ix-dev/charts/pihole/values.yaml b/library/ix-dev/charts/pihole/values.yaml index 4ffa5ba800..65724508f7 100644 --- a/library/ix-dev/charts/pihole/values.yaml +++ b/library/ix-dev/charts/pihole/values.yaml @@ -2,3 +2,35 @@ image: pullPolicy: IfNotPresent repository: pihole/pihole tag: 2023.11.0 + +resources: + limits: + cpu: 4000m + memory: 8Gi + +podOptions: + dnsConfig: + options: [] + +piholeConfig: + webPassword: '' + additionalEnvs: [] + +piholeNetwork: + webPort: 20489 + dhcp: + enabled: false + start: '' + end: '' + gateway: '' + +piholeStorage: + config: + type: ixVolume + ixVolumeConfig: + datasetName: config + dnsmasq: + type: ixVolume + ixVolumeConfig: + datasetName: dnsmasq + additionalStorages: []