From 7bae8af91c6ea1c2ee8405d730ee7feb28a7a808 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Mon, 21 Mar 2022 22:12:13 +0200 Subject: [PATCH] feat(kutt): Add kutt (#2239) * feat(kutt): Add kutt * fix vars * fix redis secrets and add more envs * update gui * remvoe persistence * root * emptyline --- charts/incubator/kutt/Chart.yaml | 37 ++ charts/incubator/kutt/questions.yaml | 392 ++++++++++++++++++ charts/incubator/kutt/templates/_secrets.tpl | 20 + .../incubator/kutt/templates/common copy.yaml | 8 + charts/incubator/kutt/values.yaml | 85 ++++ docs/_static/img/appicons/kutt.png | Bin 0 -> 13479 bytes docs/manual/default-ports.md | 5 +- 7 files changed, 545 insertions(+), 2 deletions(-) create mode 100644 charts/incubator/kutt/Chart.yaml create mode 100644 charts/incubator/kutt/questions.yaml create mode 100644 charts/incubator/kutt/templates/_secrets.tpl create mode 100644 charts/incubator/kutt/templates/common copy.yaml create mode 100644 charts/incubator/kutt/values.yaml create mode 100644 docs/_static/img/appicons/kutt.png diff --git a/charts/incubator/kutt/Chart.yaml b/charts/incubator/kutt/Chart.yaml new file mode 100644 index 00000000000..8bd0dc6c391 --- /dev/null +++ b/charts/incubator/kutt/Chart.yaml @@ -0,0 +1,37 @@ +apiVersion: v2 +appVersion: "5.6.13" +dependencies: +- name: common + repository: https://truecharts.org + version: 9.1.6 +- condition: postgresql.enabled + name: postgresql + repository: https://truecharts.org/ + version: 7.0.6 +- condition: redis.enabled + name: redis + repository: https://truecharts.org + version: 2.0.5 +deprecated: false +description: A free and open source personal finance manager +home: https://github.com/truecharts/apps/tree/master/charts/stable/kutt +icon: https://truecharts.org/_static/img/appicons/kutt.png +keywords: +- kutt +- link +- short +kubeVersion: '>=1.16.0-0' +maintainers: +- email: info@truecharts.org + name: TrueCharts + url: https://truecharts.org +name: kutt +sources: +- https://github.com/thedevs-network/kutt +type: application +version: 0.0.1 +annotations: + truecharts.org/catagories: | + - productivity + truecharts.org/SCALE-support: "true" + truecharts.org/grade: U diff --git a/charts/incubator/kutt/questions.yaml b/charts/incubator/kutt/questions.yaml new file mode 100644 index 00000000000..bb4d1b5f301 --- /dev/null +++ b/charts/incubator/kutt/questions.yaml @@ -0,0 +1,392 @@ +# Include{groups} +portals: + open: + protocols: + - "$kubernetes-resource_configmap_portal_protocol" + host: + - "$kubernetes-resource_configmap_portal_host" + ports: + - "$kubernetes-resource_configmap_portal_port" +questions: + - variable: portal + group: "Container Image" + label: "Configure Portal Button" + schema: + type: dict + hidden: true + attrs: + - variable: enabled + label: "Enable" + description: "enable the portal button" + schema: + hidden: true + editable: false + type: boolean + default: true +# Include{global} + - variable: controller + group: "Controller" + label: "" + schema: + additional_attrs: true + type: dict + attrs: + - variable: advanced + label: "Show Advanced Controller Settings" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: type + description: "Please specify type of workload to deploy" + label: "(Advanced) Controller Type" + schema: + type: string + default: "deployment" + required: true + enum: + - value: "deployment" + description: "Deployment" + - value: "statefulset" + description: "Statefulset" + - value: "daemonset" + description: "Daemonset" + - variable: replicas + description: "Number of desired pod replicas" + label: "Desired Replicas" + schema: + type: int + default: 1 + required: true + - variable: strategy + description: "Please specify type of workload to deploy" + label: "(Advanced) Update Strategy" + schema: + type: string + default: "Recreate" + required: true + enum: + - value: "Recreate" + description: "Recreate: Kill existing pods before creating new ones" + - value: "RollingUpdate" + description: "RollingUpdate: Create new pods and then kill old ones" + - value: "OnDelete" + description: "(Legacy) OnDelete: ignore .spec.template changes" +# Include{controllerExpert} + - variable: secret + group: "Container Configuration" + label: "Image Secrets" + schema: + additional_attrs: true + type: dict + attrs: + - variable: MAIL_USER + label: "MAIL_USER" + schema: + type: string + default: "" + - variable: MAIL_PASSWORD + label: "MAIL_PASSWORD" + schema: + type: string + default: "" + private: true + - variable: RECAPTCHA_SITE_KEY + label: "RECAPTCHA_SITE_KEY" + schema: + type: string + default: "" + private: true + - variable: RECAPTCHA_SECRET_KEY + label: "RECAPTCHA_SECRET_KEY" + schema: + type: string + default: "" + private: true + - variable: GOOGLE_SAFE_BROWSING_KEY + label: "GOOGLE_SAFE_BROWSING_KEY" + schema: + type: string + default: "" + private: true + - variable: GOOGLE_ANALYTICS + label: "GOOGLE_ANALYTICS" + schema: + type: string + default: "" + private: true + - variable: GOOGLE_ANALYTICS_UNIVERSAL + label: "GOOGLE_ANALYTICS_UNIVERSAL" + schema: + type: string + default: "" + private: true + + - variable: env + group: "Container Configuration" + label: "Image Environment" + schema: + additional_attrs: true + type: dict + attrs: + - variable: SITE_NAME + label: "SITE_NAME" + schema: + type: string + default: "My Kutt Instance" + - variable: DEFAULT_DOMAIN + label: "DEFAULT_DOMAIN" + schema: + type: string + default: "localhost:10195" + - variable: LINK_LENGTH + label: "LINK_LENGTH" + schema: + type: int + default: 6 + - variable: USER_LIMIT_PER_DAY + label: "USER_LIMIT_PER_DAY" + schema: + type: int + default: 50 + - variable: NON_USER_COOLDOWN + label: "NON_USER_COOLDOWN" + schema: + type: int + default: 0 + - variable: DEFAULT_MAX_STATS_PER_LINK + label: "DEFAULT_MAX_STATS_PER_LINK" + schema: + type: int + default: 5000 + - variable: DISALLOW_REGISTRATION + label: "DISALLOW_REGISTRATION" + schema: + type: boolean + default: false + - variable: DISALLOW_ANONYMOUS_LINKS + label: "DISALLOW_ANONYMOUS_LINKS" + schema: + type: boolean + default: false + - variable: CUSTOM_DOMAIN_USE_HTTPS + label: "CUSTOM_DOMAIN_USE_HTTPS" + schema: + type: boolean + default: false + - variable: ADMIN_EMAILS + label: "ADMIN_EMAILS" + schema: + type: string + default: "admin@example.com,admin@example2.com" + - variable: REPORT_EMAIL + label: "REPORT_EMAIL" + schema: + type: string + default: "admin@example.com" + - variable: CONTACT_EMAIL + label: "CONTACT_EMAIL" + schema: + type: string + default: "admin@example.com" + - variable: MAIL_HOST + label: "MAIL_HOST" + schema: + type: string + default: "" + - variable: MAIL_PORT + label: "MAIL_PORT" + schema: + type: int + default: 567 + - variable: MAIL_FROM + label: "MAIL_FROM" + schema: + type: string + default: "" + - variable: MAIL_SECURE + label: "MAIL_SECURE" + schema: + type: boolean + default: true + +# Include{containerConfig} + + - variable: service + group: "Networking and Services" + label: "Configure Service(s)" + schema: + additional_attrs: true + type: dict + attrs: + - variable: main + label: "Main Service" + description: "The Primary service on which the healthcheck runs, often the webUI" + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelector} + - variable: main + label: "Main Service Port Configuration" + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: "Port" + description: "This port exposes the container port on the service" + schema: + type: int + default: 10195 + required: true + - variable: advanced + label: "Show Advanced settings" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: protocol + label: "Port Type" + schema: + type: string + default: "HTTP" + enum: + - value: HTTP + description: "HTTP" + - value: "HTTPS" + description: "HTTPS" + - value: TCP + description: "TCP" + - value: "UDP" + description: "UDP" + - variable: nodePort + label: "Node Port (Optional)" + description: "This port gets exposed to the node. Only considered when service type is NodePort, Simple or LoadBalancer" + schema: + type: int + min: 9000 + max: 65535 + - variable: targetPort + label: "Target Port" + description: "The internal(!) port on the container the Application runs on" + schema: + type: int + default: 10195 + + - variable: serviceexpert + group: "Networking and Services" + label: "Show Expert Config" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: hostNetwork + group: "Networking and Services" + label: "Host-Networking (Complicated)" + schema: + type: boolean + default: false + +# Include{serviceExpert} + +# Include{serviceList} + +# Include{persistenceList} + + - variable: ingress + label: "" + group: "Ingress" + schema: + additional_attrs: true + type: dict + attrs: + - variable: main + label: "Main Ingress" + schema: + additional_attrs: true + type: dict + attrs: +# Include{ingressDefault} + +# Include{ingressTLS} + +# Include{ingressTraefik} + +# Include{ingressExpert} + +# Include{ingressList} + +# Include{security} + + - variable: advancedSecurity + label: "Show Advanced Security Settings" + group: "Security and Permissions" + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: securityContext + label: "Security Context" + schema: + additional_attrs: true + type: dict + attrs: + - variable: privileged + label: "Privileged mode" + schema: + type: boolean + default: false + - variable: readOnlyRootFilesystem + label: "ReadOnly Root Filesystem" + schema: + type: boolean + default: false + - variable: allowPrivilegeEscalation + label: "Allow Privilege Escalation" + schema: + type: boolean + default: false + - variable: runAsNonRoot + label: "runAsNonRoot" + schema: + type: boolean + default: false +# Include{securityContextAdvanced} + + - variable: podSecurityContext + group: "Security and Permissions" + label: "Pod Security Context" + schema: + additional_attrs: true + type: dict + attrs: + - variable: runAsUser + label: "runAsUser" + description: "The UserID of the user running the application" + schema: + type: int + default: 0 + - variable: runAsGroup + label: "runAsGroup" + description: The groupID this App of the user running the application" + schema: + type: int + default: 0 + - variable: fsGroup + label: "fsGroup" + description: "The group that should own ALL storage." + schema: + type: int + default: 568 +# Include{podSecurityContextAdvanced} + +# Include{resources} + +# Include{advanced} + +# Include{addons} diff --git a/charts/incubator/kutt/templates/_secrets.tpl b/charts/incubator/kutt/templates/_secrets.tpl new file mode 100644 index 00000000000..d3634d0df77 --- /dev/null +++ b/charts/incubator/kutt/templates/_secrets.tpl @@ -0,0 +1,20 @@ +{{/* Define the secrets */}} +{{- define "kutt.secrets" -}} +--- + +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: kutt-secrets +{{- $kuttprevious := lookup "v1" "Secret" .Release.Namespace "kutt-secrets" }} +{{- $jwt_secret := "" }} +data: + {{- if $kuttprevious}} + JWT_SECRET: {{ index $kuttprevious.data "JWT_SECRET" }} + {{- else }} + {{- $jwt_secret := randAlphaNum 32 }} + JWT_SECRET: {{ $jwt_secret | b64enc }} + {{- end }} + +{{- end -}} diff --git a/charts/incubator/kutt/templates/common copy.yaml b/charts/incubator/kutt/templates/common copy.yaml new file mode 100644 index 00000000000..edbf1b24944 --- /dev/null +++ b/charts/incubator/kutt/templates/common copy.yaml @@ -0,0 +1,8 @@ +{{/* Make sure all variables are set properly */}} +{{- include "common.setup" . }} + +{{/* Render secrets for kutt */}} +{{- include "kutt.secrets" . }} + +{{/* Render the templates */}} +{{ include "common.postSetup" . }} diff --git a/charts/incubator/kutt/values.yaml b/charts/incubator/kutt/values.yaml new file mode 100644 index 00000000000..8802554b77d --- /dev/null +++ b/charts/incubator/kutt/values.yaml @@ -0,0 +1,85 @@ +image: + repository: kutt/kutt + pullPolicy: IfNotPresent + tag: v2.7.4@sha256:2fa9480755229bd9a14ea03fb2129eb80412600f3c5dabc8f9f9fbf35ff88fce + +securityContext: + readOnlyRootFilesystem: false + runAsNonRoot: false + +podSecurityContext: + runAsUser: 0 + runAsGroup: 0 + +secret: + MAIL_USER: "" + MAIL_PASSWORD: "" + RECAPTCHA_SITE_KEY: "" + RECAPTCHA_SECRET_KEY: "" + GOOGLE_SAFE_BROWSING_KEY: "" + GOOGLE_ANALYTICS: "" + GOOGLE_ANALYTICS_UNIVERSAL: "" + +env: + PORT: "{{ .Values.service.main.ports.main.port }}" + DB_NAME: kutt + DB_USER: kutt + DB_PORT: "5432" + REDIS_PORT: "6379" + # User Defined + SITE_NAME: "My Kutt Instance" + DEFAULT_DOMAIN: "localhost:{{ .Values.service.main.ports.main.port }}" + LINK_LENGTH: 6 + USER_LIMIT_PER_DAY: 50 + NON_USER_COOLDOWN: 0 + DEFAULT_MAX_STATS_PER_LINK: 5000 + DISALLOW_REGISTRATION: false + DISALLOW_ANONYMOUS_LINKS: false + CUSTOM_DOMAIN_USE_HTTPS: false + ADMIN_EMAILS: "admin@example.com,admin@example2.com" + REPORT_EMAIL: "admin@example.com" + CONTACT_EMAIL: "admin@example.com" + MAIL_HOST: "" + MAIL_PORT: 567 + MAIL_FROM: "" + MAIL_SECURE: true + +envValueFrom: + DB_HOST: + secretKeyRef: + name: dbcreds + key: plainhost + DB_PASSWORD: + secretKeyRef: + name: dbcreds + key: postgresql-password + REDIS_HOST: + secretKeyRef: + name: rediscreds + key: plainhost + REDIS_PASSWORD: + secretKeyRef: + name: rediscreds + key: redis-password + JWT_SECRET: + secretKeyRef: + name: kutt-secrets + key: JWT_SECRET + +service: + main: + ports: + main: + port: 10195 + +# Enabled redis +redis: + enabled: true + existingSecret: "rediscreds" + +# Enabled postgres +postgresql: + enabled: true + existingSecret: "dbcreds" + postgresqlUsername: kutt + postgresqlDatabase: kutt diff --git a/docs/_static/img/appicons/kutt.png b/docs/_static/img/appicons/kutt.png new file mode 100644 index 0000000000000000000000000000000000000000..a55987404d3d045ec939f093aeba819435b8d54c GIT binary patch literal 13479 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