Giter VIP home page Giter VIP logo

helm-charts's Introduction

whoami

helm-charts's People

Contributors

bjw-s avatar halkeye avatar modesttg avatar onedr0p avatar repo-duster[bot] avatar zodiac12k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

helm-charts's Issues

New label / selector "app.kubernetes.io/component" in version 2.0.1 is set to "main", even if I use nameOverride on the main container to change it's name to something else

Details

This is primarily a question to know ensure that the functionality is intentional and hear the reasoning, not necessarily a bug.

There's a new label and selector ( app.kubernetes.io/component) added in the new version 2.0.1 of the chart.

The value of this is set to main. Even if I change the name of the main container in the pod to something other than main.

Is it intentional, that it works like this, and what is the reason?

What did you expect to happen:

I expected the value of the label / selector to be the same as the name of the main pod, but I'm no charting expert, so there is probably a reason for the functionality, that I am not aware of.

Anything else you would like to add:

No.

Additional Information:

No.

Support for Argo Rollouts Controller

Details

Is it possible to support for argo rollouts controller?

Argo Rollouts is a Kubernetes controller and set of CRDs which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes.

Describe the solution you'd like:

Example:

controller:
  type: rollout

  rollout:
    strategy:
      canary:
        # Reference to a Service which the controller will update to point to the canary ReplicaSet
        canaryService: rollouts-demo-canary
        # Reference to a Service which the controller will update to point to the stable ReplicaSet
        stableService: rollouts-demo-stable
        trafficRouting:
          nginx:
            # Reference to an Ingress which has a rule pointing to the stable service (e.g. rollouts-demo-stable)
            # This ingress will be cloned with a new name, in order to achieve NGINX traffic splitting.
            stableIngress: rollouts-demo-stable

Anything else you would like to add:

Additional Information:

Don't sort env variables

Details

What steps did you take and what happened:

use the app-template with the env vars:

env:
  # -- Config dir
  DATA_FOLDER: "config"
  A_FOLDER: "sometest"

This will lead to a e.g. deployment with:

env:
  - name: A_FOLDER
    value: sometest
  - name: DATA_FOLDER
    value: config

Env var sorting breaks dependent environment variables of kubernetes.

What did you expect to happen:

Don't change the order of env vars.

Anything else you would like to add:

Additional Information:

Allow service loadbalancer modifier

Details

Describe the solution you'd like:

With the Kubernetes version 1.24 you can add the service config like below:

spec.allocateLoadBalancerNodePorts: false/true

This allows the LB service to not create NodePort when the LB service is created.
The feature is now stable.

Anything else you would like to add:

Would it be possible to enable this option to be enabled when creating a LoadBalancer service Type.

https://kubernetes.io/docs/concepts/services-networking/service/#load-balancer-nodeport-allocation

Allow to mount configmaps

Details

For example i use blocky dns which gathers it configuration from data/config.yaml
I can write new configmap from chart, but i can't mount it as some path

Documentation example for multiple deployments in one chart

My app has multiple independent deployments. They have separate readiness, scheduling, etc; but need to be deployed with the same image.

It'd be great to have an example of how to use the new common chart and do extra "custom" deployments sharing the base templates and configurations. In my case the deployments are all the same except the command and args.

Ideally each deployment would also have a headless service to target with a ServiceMonitor.

Is this possible to do with the new common chart, or would I be better off making separate charts and having a meta chart with the others as a dependency?

Support mulitline stringData secret

Details

What steps did you take and what happened:

Inject a x509 certificate as stringData for a secret. I tried multiple yaml formatters but failed (| or |-)

secrets:
  secret:
    enabled: true
    stringData: |-
      aldfaldflaf
      adfadfadf

What did you expect to happen:

That the secret just has a literal block with

stringData: |-
    aldfaldflaf
    adfadfadf

but instead it is rendered as

stringData:
  |-
    aldfaldflaf
    adfadfadf

Anything else you would like to add:

Additional Information:

Include examples folder for use of app-template

Details

Describe the solution you'd like:

It might be nice to cover some usage examples using various methods of installing a app. There's only 3 methods of installation I can think of:

  • flux helm release
  • helm install -f values.yaml
  • kustomize w/ helm

Upgrade 1.x - >2.x, can't becuase labelselector is immutable

Details

What steps did you take and what happened:

Tried to upgrade 1.5.1 -> 2.x, followed the values.yaml changes as best i could. Ran through argoCD to update, it fails to deploy because of an immutable field

exact error:

Deployment.apps "sonarr" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app.kubernetes.io/component":"main", "app.kubernetes.io/instance":"sonarr", "app.kubernetes.io/name":"sonarr"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable

1.5.1 format:

image:
  repository: ghcr.io/onedr0p/sonarr-develop
  pullPolicy: IfNotPresent
  tag: 4.0.0.688
strategy:
  type: Recreate
podSecurityContext:
  runAsUser: 1000
  runAsGroup: 1000
  fsGroup: 1000
  fsGroupChangePolicy: "OnRootMismatch"
env:
  TZ: America/Los_Angeles
  SONARR__API_KEY:
    valueFrom:
      secretKeyRef:
        name: sonarr-api-key
        key: sonarr_api_key
service:
  main:
    ports:
      http:
        port: 8989
ingress:
  main:
    enabled: true
    ingressClassName: traefik
    hosts:
      - host: server.domain.tld
        paths:
          - path: /
            pathType: Prefix
            service:
              port: 8989
persistence:
  config:
    enabled: true
    type: pvc
    accessMode: ReadWriteOnce
    size: 15Gi
    mountPath: "/config"
  media:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/media
    mountPath: "/media-nfs"
  backups:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/dockerdata/backups/sonarr
    mountPath: "/config/Backups"
  dockerdata:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/dockerdata/
    mountPath: "/mnt/dockerdata"
probes:
  liveness:
    enabled: true
    custom: true
    spec:
      httpGet:
        path: /
        port: http
resources:
  requests:
    cpu: 300m
    memory: 768Mi
  limits:
    memory: 3Gi

2.x format:

defaultPodOptions:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    fsGroupChangePolicy: "OnRootMismatch"
controllers:
  main:
    containers:
      main:
        strategy:
          type: Recreate
        image:
          repository: ghcr.io/onedr0p/sonarr-develop
          pullPolicy: IfNotPresent
          tag: 4.0.0.688
        probes:
          liveness:
            enabled: true
            custom: true
            spec:
              httpGet:
                path: /
                port: http
        env:
          TZ: America/Los_Angeles
          SONARR__API_KEY:
            valueFrom:
              secretKeyRef:
                name: sonarr-api-key
                key: sonarr_api_key
        resources:
          requests:
            cpu: 300m
            memory: 768Mi
          limits:
            memory: 3Gi
service:
  main:
    ports:
      http:
        port: 8989

ingress:
  main:
    enabled: true
    className: traefik
    hosts:
      - host: server.domain.tld
        paths:
          - path: /
            pathType: Prefix
            service:
              name: main
              port: 8989
persistence:
  config:
    enabled: true
    type: persistentVolumeClaim
    accessMode: ReadWriteOnce
    size: 15Gi
    globalMounts:
      - path: "/config"
  media:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/media
    globalMounts:
      - path: "/media-nfs"
  backups:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/dockerdata/backups/sonarr
    globalMounts:
      - path: "/config/Backups"
  dockerdata:
    enabled: true
    type: nfs
    server: server.domain.tld
    path: /mnt/sirius/dockerdata/
    globalMounts:
      - path: "/mnt/dockerdata"

am I missing something here? the values seem to lint fine to ArgoCD. thanks! FWIW, this is what ArgoCD shows as the deltas:
image

And the section in particular:
image

RBAC

Please provide support for RBAC creation.

imagePullSecret not working with 2.0.0-beta.1

Details

What steps did you take and what happened:

I was migrating to the latest version 2.0.0-beta.1 and have seen that imagePullSecrets are currently not able to be set.

i was using the following syntax

defaultPodOptions:
  imagePullSecrets:
    - name: my-pull-secret

but this causes the following error:

Helm upgrade failed: template: app-template/templates/common.yaml:14:3: executing "app-template/templates/common.yaml" at <include "bjw-s.common.loader.generate" .>: error calling include: template: app-template/charts/common/templates/loader/_generate.tpl:8:6: executing "bjw-s.common.loader.generate" at <include "bjw-s.common.render.controllers" .>: error calling include: template: app-template/charts/common/templates/render/_controllers.tpl:25:12: executing "bjw-s.common.render.controllers" at <include "bjw-s.common.class.deployment" (dict "rootContext" $ "object" $deploymentObject)>: error calling include: template: app-template/charts/common/templates/classes/_deployment.tpl:59:13: executing "bjw-s.common.class.deployment" at <include "bjw-s.common.lib.pod.spec" (dict "rootContext" $rootContext "controllerObject" $deploymentObject)>: error calling include: template: app-template/charts/common/templates/lib/pod/_spec.tpl:38:25: executing "bjw-s.common.lib.pod.spec" at <trim>: wrong type for value; expected string; got []interface {}

What did you expect to happen:

The image pull secret is applied to all pods

Anything else you would like to add:

Additional Information:

Multiple Images

First off, thank you for the template, it works brilliantly.

I have written a number of values files successfully for services that are a single microservice (no external database).

In the examples, I did not see anything that uses more than one image, nor did I see a way to use multiple in the provided values file.

How would I create something like a web service with a postgresql database?

Preserve order of `initContainers`

Details

What steps did you take and what happened:

It looks like initContainers ordering is not preserved when the values are templated out.

https://github.com/onedr0p/home-ops/blob/576dd2ccee7de3351e6e648ad868189f755c15d2/kubernetes/apps/default/guacamole/app/guacamole/helmrelease.yaml#L33-L65

This gets templated out like

      initContainers:
      - args:
        - -c
        - |
          /opt/guacamole/bin/initdb.sh --postgres > /migrations/init.sql
        command:
        - /bin/sh
        envFrom:
        - secretRef:
            name: guacamole-secret
        image: docker.io/guacamole/guacamole:1.5.0
        imagePullPolicy: IfNotPresent
        name: create-schema
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /migrations
          name: migrations
      - envFrom:
        - secretRef:
            name: guacamole-secret
        image: ghcr.io/onedr0p/postgres-initdb:14.6
        imagePullPolicy: IfNotPresent
        name: init-db
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      - args:
        - -c
        - |
          export PGPASSWORD=$(POSTGRES_PASSWORD)
          psql -h $(POSTGRES_HOSTNAME) -d $(POSTGRES_DATABASE) -U $(POSTGRES_USER) -a -w -f /migrations/init.sql || true
        command:
        - /bin/bash
        envFrom:
        - secretRef:
            name: guacamole-secret
        image: ghcr.io/onedr0p/postgres-initdb:14.6
        imagePullPolicy: IfNotPresent
        name: run-migrations
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /migrations
          name: migrations

It is my understanding that initContainers run in the order specified.

.Values.secret creates envFrom in container but not Secret object

Details

What steps did you take and what happened:

Deploying an app with .Values.secret sets sets an envFrom field in the bjw-s.common.lib.controller.mainContainer with the release name as the secret name, but does not create a Secret object.

Given that .Values.secret does not appear in the main values.yaml file, it seems like this is a holdover from k8s-at-home/common chart. It looks like .Values.secrets is supposed to replace this functionality.

What did you expect to happen:

Either a Secret object is created when .Values.secret is populated, or the envFrom: field is removed entirely.

Anything else you would like to add:

N/A

Additional Information:

N/A

Usage of persistence.advancedMounts when there are initContainers causes errors (common v2.0.0)

Details

What steps did you take and what happened:

When both persistence.advancedMounts and controllers.*.initContainers are specified in values.yaml, installing or
upgrading the chart (app-template) will fail with "error calling dig: interface conversion: interface {} is nil, not
string".

Failed example

helm upgrade -i release-name bjw-s/app-template --version 2.0.0 -f values.yaml

controllers:
  main:
    containers:
      main:
        image:
          repository: ghcr.io/mendhak/http-https-echo
          tag: 30

    initContainers:
      a-container:
        image:
          repository: ghcr.io/mendhak/http-https-echo
          tag: 30

service:
  main:
    enabled: false

persistence:
  config:
    enabled: true
    advancedMounts:
      main:
        main:
          - path: /data/config.yaml
            readOnly: false
            subPath: config.yaml
Successful example (same as above, with initContainers commented)

helm upgrade -i release-name bjw-s/app-template --version 2.0.0 -f values.yaml

controllers:
  main:
    containers:
      main:
        image:
          repository: ghcr.io/mendhak/http-https-echo
          tag: 30

    # initContainers:
      a-container:
        image:
          repository: ghcr.io/mendhak/http-https-echo
          tag: 30

service:
  main:
    enabled: false

persistence:
  config:
    enabled: true
    advancedMounts:
      main:
        main:
          - path: /data/config.yaml
            readOnly: false
            subPath: config.yaml
Error output
Error: UPGRADE FAILED: template: app-template/templates/common.yaml:14:3: executing "app-template/templates/common.yaml" at <include "bjw-s.common.loader.generate" .>: 
error calling include: template: app-template/charts/common/templates/loader/_generate.tpl:8:6: executing "bjw-s.common.loader.generate" at <include "bjw-s.common.render.controllers" .>: 
error calling include: template: app-template/charts/common/templates/render/_controllers.tpl:25:12: executing "bjw-s.common.render.controllers" at <include "bjw-s.common.class.deployment" (dict "rootContext" $ "object" $deploymentObject)>: 
error calling include: template: app-template/charts/common/templates/classes/_deployment.tpl:59:13: executing "bjw-s.common.class.deployment" at <include "bjw-s.common.lib.pod.spec" (dict "rootContext" $rootContext "controllerObject" $deploymentObject)>: 
error calling include: template: app-template/charts/common/templates/lib/pod/_spec.tpl:58:12: executing "bjw-s.common.lib.pod.spec" at <include "bjw-s.common.lib.pod.field.initContainers" (dict "ctx" $ctx)>: 
error calling include: template: app-template/charts/common/templates/lib/pod/fields/_initContainers.tpl:28:32: executing "bjw-s.common.lib.pod.field.initContainers" at <include "bjw-s.common.lib.container.spec" (dict "rootContext" $rootContext "containerObject" $containerObject)>: 
error calling include: template: app-template/charts/common/templates/lib/container/_spec.tpl:48:12: executing "bjw-s.common.lib.container.spec" at <include "bjw-s.common.lib.container.field.volumeMounts" (dict "ctx" $ctx)>: 
error calling include: template: app-template/charts/common/templates/lib/container/fields/_volumeMounts.tpl:38:33: executing "bjw-s.common.lib.container.field.volumeMounts" at <dig $controllerObject.identifier $containerObject.identifier list .advancedMounts>: 
error calling dig: interface conversion: interface {} is nil, not string

(Examples are a reduced version of advanced-values.yaml)

What did you expect to happen:

The chart should install successfully, with the volumeMounts rendered as expected for both main and init containers (or
ignored completely, if advancedMounts was not designed to include init containers).

Anything else you would like to add:

Additional Information:

I noticed that controllers.*.initContainers.*.volumeMounts is no longer applied (as in v1.5.1), and that
persistence.globalMounts applies to init containers. Does this imply that persistence.advancedMounts was intended to
also apply to init containers?

When service 'http' is not defined, the service object gets rendered incorrectly

Details

What steps did you take and what happened:

I've tried to use your template to deploy a factorio server - which doesn't have any http endpoints (or a service called 'http' for that matter')
I've found that when service.main.ports.http is not set, the template will render it in the service as an empty ports, and thus failing the validation done by kubernetes.

Helm debugging showed the service being rendered as this:

# Source: app-template/templates/common.yaml
apiVersion: v1
kind: Service
metadata:
  name: factorio
  labels:
    app.kubernetes.io/service: factorio
    app.kubernetes.io/instance: factorio
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: factorio
    helm.sh/chart: app-template-1.3.2
  annotations:
spec:
  type: ClusterIP
  ports:
    - port: 34197
      targetPort: 34197
      protocol: UDP
      name: game
    - port:
      targetPort: http
      protocol: TCP
      name: http
    - port: 27015
      targetPort: 27015
      protocol: TCP
      name: steam
  selector:
    app.kubernetes.io/instance: factorio
    app.kubernetes.io/name: factorio
---

When setting service.main.ports.http.enabled explicitly to false the problem disappears.
However, when someone isn't aware of this mechanic kubernetes will only throw the
spec.ports[1].port: Invalid value: 0: must be between 1 and 65535, inclusive making it very ambiguous to debug.

This issue happens because the template's init file makes a deepcopy reference to values.yaml which contains the defined http service with a non-defined port.

To resolve the issue

I could dive deeper into this, haven't really done much with writing helm templates before.
Basically my first guess would be to check whether the user has defined addtional services other then the default 'http' service and to omit the 'default' service if that's the case.

I hope this helps!

Kind regards,
Sem

Knative Service Controller Type

Details

Describe the solution you'd like:

I have recently started migrating some less used deployments to Knative, which can scale to 0 until an ingress request occurs.

To deploy these "serverless" containers, Knative uses its own Service CRD which is basically a combination of a standard Deployment, Service, and Ingress. They have a good example and comparison at Converting a Kubernetes Deployment to a Knative Service.

I would like to implement a new controller type for for this, but want to check first to see if there's any sort of desire for this and if a PR would be accepted. Knative obviously doesn't ship with Kubernetes so I don't want to confuse users of the common library.

Anything else you would like to add:

It would be nice if the common library could create a Knative service with a new controller type called something like knative-service.

This may be complicated to implement since it would affect how services and ingresses are created, but it has been frustrating to completely scrap Helm when I migrate a site.

Additional Information:

I really enjoy using the common library chart. Thank you for everything that you do!

Ingress port must be of type Int32

Details

What steps did you take and what happened:

I tried to deploy the example app based on the template provided (which specifies the ingress's backend service port as the strings http and websockets, rather than a number).

But I get the error:

cannot unmarshal string into Go struct field ServiceBackendPort.spec.rules.http.paths.backend.service.port.number of type int32

What did you expect to happen:

I expected the example template to work out of the box. Additionally, the examples themselves made me think I would be able to specify the ingress' backend service ports via their service port name, rather than just a numbered port.

Anything else you would like to add:

The Ingress resources' ServiceBackenPort allows for either a number or name property. So, it could be possible to support this usage if we wanted

Side question: I am noticing that there is little-to-no reference/examples in the main Kubernetes documentation to using the service port name as reference... is it's usage really uncommon and/or discouraged for some reason I am unaware of?

Additional Information:

I'd be happy to help with a PR, but would first look for guidance on your preferred solution. Example:

  1. Just update the examples/docs to use the port numbers (or &/* yaml anchors/aliases)
  2. Update the template Chart itself to support allowing port name references (ex: using kindIs or an explicit portName property)
  3. Both
  4. Something else?

How to add matchLabels?

Details

Is there anyway we can specify matchLabels for deployments/statefulsets/daemonsets?

I think the only thing i saw that might make this possible for ds (in my case) is to set topology constraints?

Cheers,

ServiceAccount does not work with 1.25+

Details

Using the following config on Kubernetes 1.25 does not adhere to the changes made to ServiceAccounts in 1.25

values:
  serviceAccount:
    create: true
    name: *app

In 1.25 we need to define the Secret and link it to the SA:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
secrets:
  - name: vault-auth
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: vault-auth
  annotations:
    kubernetes.io/service-account.name: vault-auth

Support for prometheusRule

Details

I would like the common chart to be able to support prometheusRule, which goes nicely with the existing serviceMonitor.

Example:

# -- Enable and configure Prometheus Rules for the chart under this key.
prometheusRule:
  enabled: true
  labels: {}
  # -- Configure additionial rules for the chart under this key.
  rules:
    - alert: SonarrDown
      annotations:
      description: Sonarr service is down.
      summary: Sonarr is down.
      expr: |
        sonarr_system_status == 0
        for: 5m
        labels:
          severity: critical

Anything else you would like to add:

Additional Information:

Release notes for 2.0?

I've looked all over but can't find a release notes file for the 2.0 release, is it somewhere I'm missing?

Add finalizers to meta data

Details

Describe the solution you'd like:

An additional field that would allow you to add Finalizers to resources.

Anything else you would like to add:

Sorry if you can already do this and I have missed it!
I am happy to make a PR to try and do it but would be good to know where you think it would be best to go.

How to wire up the CI

Hi there. I have adopted some of the k8s-at-home charts so I can continue to maintain them, in k8s-at-home/charts#1762

It was suggested to me to clone this repo and benefit from the CI on my own repo of charts. I've got most of it working but some of the steps seem to need a GitHub application. I haven't used one of these before, do you have any notes about how you set this up? What permissions etc.

It would be super useful to me, and probably to anyone else who wants to rehome a kah chart. Thanks

Allow adding a prefix to Ingress names or overriding them entirely

Details

Currently there's a property nameOverride: under ingress:, where you can override the suffix used for an Ingress' name, see: https://github.com/bjw-s/helm-charts/blob/main/charts/library/common/values.yaml

We would like to be able to add a prefix instead, unless there's a specific reason, this is a bad idea?

Our case is that we have the same service in multiple namespaces in our test environment, and we would like the ingress names to be prefixed with the namespace name.

Anything else you would like to add:

No.

Additional Information:

No.

Feature Request: support arbitrary kubernetes resources in values

Details

Describe the solution you'd like:

Some applications from k8s-at-home like radarr had additional resources beyond what the common chart provided. It would be useful to be able to specify extra resources like this directly in the values for app-templates.

The bedag/raw chart provides a good model for doing this. The values .Values.resources and .Values.templates are provided as arrays of kubernetes resource objects that are rendered into the helm chart, with templates rendered with the tpl function.

Because resources is already a reserved value in the top level of app-template, I propose .Values.extraManifests and .Values.extraManifestTemplates (though if anyone wants to propose something better, go for it).

Additional Information:
Links:
https://github.com/bedag/helm-charts/tree/master/charts/raw
https://artifacthub.io/packages/helm/main/raw

The bedag/raw template is licensed under Apache-2.0

CronJob spec not quite there

Details

Saw that the app-template can create a cron-job resource, so I gave it a try and it didn't get a few things.

values.yaml

    controller:
      type: cronjob
      cronjob:
        schedule: "@daily"
        ttlSecondsAfterFinished: 43200
    restartPolicy: OnFailure
    image:
      repository: docker.io/prodrigestivill/postgres-backup-local
      tag: 15@sha256:93f597a7ad95d5a9d988e57c32442e6f78d6e88d809ca96bb0a3270e45c85650
      pullPolicy: IfNotPresent
    command: ["/backup.sh"]
    env:
      - name: POSTGRES_HOST
        value: postgres-ro.dbms.svc.cluster.local
      - name: POSTGRES_USER
        valueFrom:
          secretKeyRef:
            name: postgres-superuser
            key: POSTGRES_SUPER_USER
      - name: POSTGRES_PASSWORD
        valueFrom:
          secretKeyRef:
            name: postgres-superuser
            key: POSTGRES_SUPER_PASS
      - name: POSTGRES_DB
        value: "outline,shlink,teslamate"
      - name: BACKUP_KEEP_DAYS
        value: "7"
      - name: BACKUP_KEEP_WEEKS
        value: "4"
      - name: BACKUP_KEEP_MONTHS
        value: "6"
      - name: BACKUP_KEEP_MINS
        value: "1440"
      - name: POSTGRES_EXTRA_OPTS
        value: "-b -C -Z6"
    enableServiceLinks: false
    service:
      main:
        enabled: false
    persistence:
      backups:
        enabled: true
        existingClaim: postgres-backups
        mountPath: /backups
    podSecurityContext:
      runAsUser: 568
      runAsGroup: 568
      fsGroup: 568
      fsGroupChangePolicy: "OnRootMismatch"

output.yaml

---
# Source: app-template/templates/common.yaml
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: postgres-backup
  labels:
    app.kubernetes.io/instance: postgres-backup
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: postgres-backup
    helm.sh/chart: app-template-1.2.1
spec:
  concurrencyPolicy: "Forbid"
  startingDeadlineSeconds: 30
  schedule: "@daily"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 43200
      template:
        metadata:
          labels:
            app.kubernetes.io/name: postgres-backup
            app.kubernetes.io/instance: postgres-backup
        spec:
          
          serviceAccountName: default
          automountServiceAccountToken: true
          securityContext:
            fsGroup: 568
            fsGroupChangePolicy: OnRootMismatch
            runAsGroup: 568
            runAsUser: 568
          dnsPolicy: ClusterFirst
          enableServiceLinks: false
          containers:
            - name: postgres-backup
              image: "docker.io/prodrigestivill/postgres-backup-local:15@sha256:93f597a7ad95d5a9d988e57c32442e6f78d6e88d809ca96bb0a3270e45c85650"
              imagePullPolicy: IfNotPresent
              command:
                  
                - /backup.sh
              env:
                - name: POSTGRES_HOST
                  value: postgres-ro.dbms.svc.cluster.local
                - name: POSTGRES_USER
                  valueFrom:
                    secretKeyRef:
                      key: POSTGRES_SUPER_USER
                      name: postgres-superuser
                - name: POSTGRES_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      key: POSTGRES_SUPER_PASS
                      name: postgres-superuser
                - name: POSTGRES_DB
                  value: outline,shlink,teslamate
                - name: BACKUP_KEEP_DAYS
                  value: "7"
                - name: BACKUP_KEEP_WEEKS
                  value: "4"
                - name: BACKUP_KEEP_MONTHS
                  value: "6"
                - name: BACKUP_KEEP_MINS
                  value: "1440"
                - name: POSTGRES_EXTRA_OPTS
                  value: -b -C -Z6
              ports:
                
              volumeMounts:
                - name: backups
                  mountPath: /backups
              livenessProbe:
              readinessProbe:
              startupProbe:
          volumes:
            - name: backups
              persistentVolumeClaim:
                claimName: postgres-backups
          restartPolicy: Never

command:

helm template postgres-backup -n dbms bjw-s/app-template -f values.yaml > output.yaml

What did you expect to happen:

The ports: entry shouldn't be present because there aren't any ports.

As well, the livenessProbe, readinessProbe, and startupProbe are specified but don't have any defaults. (or shouldn't be there)

Anything else you would like to add:

Not sure if this is a desired use of the template at all, but it did work for recyclarr (from Devin of course)

Additional Information:

"ports" property missing under main container in new version 2.0.1

Details

This is primarily a question to know ensure that the change below was intentional and hear the reasoning, not necessarily a bug.

I upgraded my Helm chart from using version 1.5.1 to 2.0.1 and compared the rendered result.

Previously I automatically got a ports property under the main container in the rendered result, like this:

    spec:
...
      containers:
        - name: ...
...
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP

... in the new version I have to add it myself manually in my values-file, like this:

controllers:
  main:
    containers:
      main:
...
        ports:
          - containerPort: 8080
            name: http
            protocol: TCP

... otherwise it is not rendered. Was this change on purpose, and what is the reason?

What did you expect to happen:

I expected the new version to render the same as the old version for ports, but I'm no charting expert, so there is probably a reason for the change, that I am not aware of.

Anything else you would like to add:

No.

Additional Information:

No.

RFC: Option to enable a pre-upgrade helm hook that is able to snapshot the config PVC via a job

Details

Describe the solution you'd like:

An option to enable a pre-upgrade helm hook that is able to snapshot the config PVC via a Kubernetes Job with the external-snapshotter.


For example this job would get ran before helm upgrade:

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ .Release.Name }}"
  labels:
    app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
    app.kubernetes.io/instance: {{ .Release.Name | quote }}
    app.kubernetes.io/version: {{ .Chart.AppVersion }}
    helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "1"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    metadata:
      name: "{{ .Release.Name }}"
      labels:
        app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
        app.kubernetes.io/instance: {{ .Release.Name | quote }}
        helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
    spec:
      restartPolicy: Never
      containers:
      - name: snapshot-volume
        image: "bjw-s/snapshotter:1.0.0"
        command: ["run","snapshot"]

Maybe it could be enabled via a new option with an array of volumes for the Job to be ran for:

values:
  snapshotHooks:
    - volume: config
    - volume: test

Or, included in persistence and volumeClaimTemplates blocks:

values:
  persistence:
    config:
      enabled: true
      existingClaim: config
      snapshotBeforeUpgrade: true
values:
  volumeClaimTemplates:
    - name: config
      mountPath: /config
      accessMode: ReadWriteOnce
      size: 10Gi
      storageClass: ceph-block
      snapshotBeforeUpgrade: true
      labels:
        snapshot.home.arpa/enabled: "true"

Having something like this would make reverting back to a previous version of an application with the matching data volume given you change your PVC to the snapshot taken via dataSource

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: restore-pvc-1
spec:
  # ...
  dataSource:
    name: snapshot-name
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

Few areas of concern I have are:

  • The container in the job needs to have access to either snapshot via kubectl or another method.
  • The external snapshotter and CRDs need to be installed before this is enabled, we could probably check that it's in the cluster with Capabilities via helm

Clarify this repo's purpose?

๐Ÿ‘‹ Welcome to my Helm Charts repository. This repo contains Helm charts that I have developed to run applications in my [home Kubernetes cluster](https://github.com/bjw-s/home-ops/).

This repo contains Helm charts that I have developed to run applications in my home Kubernetes cluster.

Isn't this only containing the library chart? Or are there other charts that I just failed to find? :-)

Add support for networkpolicy

Details

Describe the solution you'd like:

I could not find a way to add fully fledged networkpolicies (ingress/egress) to my workload.

Anything else you would like to add:

I found a way to deploy egress network policies by "hacking" them into the vpn addon, unfortunately it is only possible to add egress policies.

addons:
  vpn:
    enabled: true
    type: ""
    networkPolicy:
      enabled: true
      egress:
      - to:
        - ipBlock:
            cidr: 0.0.0.0/0

This config allowed me to get egress network policies, but unfortunately no ingress

Additional Information:

I would be interested in implementing this feature. Thank you for this great helm chart ๐Ÿฅณ๐Ÿ™

expected map[string]interface {}; got bool in _ports.tpl

Details

receiving an error expected map[string]interface {}; got bool in library/common/templates/lib/container/_ports.tpl. Upon closer inspection of the code, it appears that {{- $serviceEnabled = $service.enabled -}} on line 9 is missing :=

Another thing I noticed is that $serviceEnabled is being set to true on line 7, so that would make that cast as a bool twice.

I don't use helm very often, especially on such detailed library so I could be mistaken here.

What steps did you take and what happened:

I am using the xbvr helm chart which relies on this library as a submodule.

I am passing in the following in to .values in terraform

` set {
name = "service.enabled"
value = "true"
}

set {
name = "service.type"
value = "loadBalancer"
}

set {
name = "service.loadBalancerIP"
value = "${var.loadBalancerIP}"
}
`

What did you expect to happen:

I expected the flags set to configure to metallb with a fixed ip address and enable the service

Anything else you would like to add:

Here is the full error that I have received:
โ”‚ Error: template: xbvr/templates/common.yaml:1:4: executing "xbvr/templates/common.yaml" at <include "bjw-s.common.loader.all" .>: error calling include: template: xbvr/charts/common/templates/loader/_all.tpl:9:6: executing "bjw-s.common.loader.all" at <include "bjw-s.common.loader.generate" .>: error calling include: template: xbvr/charts/common/templates/loader/_generate.tpl:23:6: executing "bjw-s.common.loader.generate" at <include "bjw-s.common.render.controller" .>: error calling include: template: xbvr/charts/common/templates/render/_controller.tpl:7:10: executing "bjw-s.common.render.controller" at <include "bjw-s.common.class.deployment" .>: error calling include: template: xbvr/charts/common/templates/classes/_deployment.tpl:54:10: executing "bjw-s.common.class.deployment" at <include "bjw-s.common.lib.controller.pod" .>: error calling include: template: xbvr/charts/common/templates/lib/controller/_pod.tpl:70:6: executing "bjw-s.common.lib.controller.pod" at <include "bjw-s.common.lib.controller.mainContainer" .>: error calling include: template: xbvr/charts/common/templates/lib/controller/_mainContainer.tpl:45:12: executing "bjw-s.common.lib.controller.mainContainer" at <include "bjw-s.common.lib.container.ports" .>: error calling include: template: xbvr/charts/common/templates/lib/container/_ports.tpl:8:18: executing "bjw-s.common.lib.container.ports" at <$service>: wrong type for value; expected map[string]interface {}; got bool

Code Server git deployKeySecret has permissions ug+r instead of just u+r

Details

When using the code server add-on in app-template, the defaultMode is specified as 256 and perhaps should actually be 100 (hex vs decimal?)

    addons:
      codeserver:
        enabled: true
        image:
          repository: codercom/code-server
          tag: 4.7.0
        workingDir: "/config"
        git:
          deployKeySecret: home-assistant-code-server
root@home-assistant-7647895d87-j8jvn:~/.ssh# ls -l
total 4
-r--r----- 1 root coder 3434 Sep 12 01:04 id_rsa

What steps did you take and what happened:

What did you expect to happen:

Permissions should just be u+r

Anything else you would like to add:

Additional Information:

cephfs mount

Details

Describe the solution you'd like:

Hi! It would be very handy to not only mount pvc, emptyDir, hostPath and nfs within the persistence section but also ceph.
As of the docs it does not need a lot of configuration expect the monitors on which the ceph-storage is running.

At the end, it would be nice to configure it in the values.yaml as the following:

persistence:
   cephfs-config:
     enabled: true
     type: cephfs
     monitors:
      - mon.rook.svc.cluster.local:6790
      - mon.rook.svc.cluster.local:6789
     path: /registryFS
     secretRef:
       name: rook-rbd-user

Anything else you would like to add:

If there is no intention to add this, I can create a PR and try to implement it. I am relatively new to k8s, helm and everything but would put in the effort to create the PR properly.

Thanks in advance!

Additional examples (additionalContainers)

Details

Describe the solution you'd like:

I frequently would like to add an additionalContainer (webserver, etc) to a deployment, but it's not clear to me how the additionalContainer objects map. If I wanted to define an additional container image/tag, environmental variables, attach secrets/configmaps, and include a service, how can I define all that in the additionalContainers object? Do some things (i.e., services(?) get defined with the main image?

An example would go a long way to helping sort this out.

Anything else you would like to add:

Chart is incredibly useful -- thank you!

RFC: Add basic serviceMonitor support into library chart

Details

Describe the solution you'd like:

Using the library chart with applications that export metrics requires the user to write a separate serviceMonitor manifests.

Add basic support for embedding a serviceMonitor in the library chart

For example:

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: &app cloudflare-exporter
  namespace: monitoring
spec:
  interval: 15m
  chart:
    spec:
      chart: kah-common-chart
      version: 1.2.2
      sourceRef:
        kind: HelmRepository
        name: k8s-at-home-charts
        namespace: flux-system
      interval: 15m
  install:
    createNamespace: true
    remediation:
      retries: 5
  upgrade:
    remediation:
      retries: 5
  values:
    global:
      nameOverride: *app
    controller:
      strategy: RollingUpdate
    image:
      repository: ghcr.io/lablabs/cloudflare_exporter
      tag: 0.0.13
    env:
      FREE_TIER: "true"
    envFrom:
      - secretRef:
          name: *app
    service:
      main:
        ports:
          http:
            port: 8080
    # Full Options for Service Monitor
    serviceMonitor:
      enabled: true
      endpoints:
        - port: http
          path: /metrics
          interval: 1m
          scrapeTimeout: 10s

Open to any thoughts on this, maybe we pass the full spec thru to the serviceMonitor omitting the spec.selector which can be auto populated?

---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: &app cloudflare-exporter
  namespace: monitoring
  labels: &labels
    app.kubernetes.io/instance: *app
    app.kubernetes.io/name: *app
spec:
  selector:
    matchLabels:
      <<: *labels
  endpoints:
    - port: http
      scheme: http
      path: /metrics
      interval: 1m
      scrapeTimeout: 10s

VPN addon for v2

Details

Request that the VPN addon (That was there in v1.5.5) be added in v2 as well

Anything else you would like to add:
The add-ons were a great functionality that was included in v1.5.1
Would really appreciate it if it could be made available in the latest version as well.

Add a JSON schema

Since Helm version 3 you can use JSON schema files to validate Helm values as stated on the docs: https://helm.sh/docs/topics/charts/#schema-files

This values.schema.json can be used by different helm commands to validate the values.yml, also could be used by the yaml language server of your editor to validate the changes as you make them.

I've tried to create a schema usin https://codebeautify.org using a simple HelmRelease file and pasting the contents of values.yaml inside the values field, wich generates the following JSON schema:

Long schema file
{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "$ref": "#/definitions/Welcome9",
    "definitions": {
        "Welcome9": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "apiVersion": {
                    "type": "string"
                },
                "kind": {
                    "type": "string"
                },
                "metadata": {
                    "$ref": "#/definitions/Metadata"
                },
                "spec": {
                    "$ref": "#/definitions/Welcome9Spec"
                }
            },
            "required": [
                "apiVersion",
                "kind",
                "metadata",
                "spec"
            ],
            "title": "Welcome9"
        },
        "Metadata": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "name": {
                    "type": "string"
                },
                "namespace": {
                    "type": "string"
                }
            },
            "required": [
                "name",
                "namespace"
            ],
            "title": "Metadata"
        },
        "Welcome9Spec": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "interval": {
                    "type": "string"
                },
                "chart": {
                    "$ref": "#/definitions/Chart"
                },
                "install": {
                    "$ref": "#/definitions/Install"
                },
                "upgrade": {
                    "$ref": "#/definitions/Upgrade"
                },
                "values": {
                    "$ref": "#/definitions/Values"
                }
            },
            "required": [
                "chart",
                "install",
                "interval",
                "upgrade",
                "values"
            ],
            "title": "Welcome9Spec"
        },
        "Chart": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "spec": {
                    "$ref": "#/definitions/ChartSpec"
                }
            },
            "required": [
                "spec"
            ],
            "title": "Chart"
        },
        "ChartSpec": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "chart": {
                    "type": "string"
                },
                "version": {
                    "type": "string"
                },
                "sourceRef": {
                    "$ref": "#/definitions/SourceRef"
                },
                "interval": {
                    "type": "string"
                }
            },
            "required": [
                "chart",
                "interval",
                "sourceRef",
                "version"
            ],
            "title": "ChartSpec"
        },
        "SourceRef": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "kind": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "namespace": {
                    "type": "string"
                }
            },
            "required": [
                "kind",
                "name",
                "namespace"
            ],
            "title": "SourceRef"
        },
        "Install": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "createNamespace": {
                    "type": "boolean"
                },
                "remediation": {
                    "$ref": "#/definitions/Remediation"
                }
            },
            "required": [
                "createNamespace",
                "remediation"
            ],
            "title": "Install"
        },
        "Remediation": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "retries": {
                    "type": "integer"
                }
            },
            "required": [
                "retries"
            ],
            "title": "Remediation"
        },
        "Upgrade": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "remediation": {
                    "$ref": "#/definitions/Remediation"
                }
            },
            "required": [
                "remediation"
            ],
            "title": "Upgrade"
        },
        "Values": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "global": {
                    "$ref": "#/definitions/Global"
                },
                "controller": {
                    "$ref": "#/definitions/Controller"
                },
                "image": {
                    "$ref": "#/definitions/Image"
                },
                "imagePullSecrets": {
                    "type": "array",
                    "items": {}
                },
                "command": {
                    "type": "array",
                    "items": {}
                },
                "args": {
                    "type": "array",
                    "items": {}
                },
                "podAnnotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "podLabels": {
                    "$ref": "#/definitions/Affinity"
                },
                "serviceAccount": {
                    "$ref": "#/definitions/ServiceAccount"
                },
                "automountServiceAccountToken": {
                    "type": "boolean"
                },
                "secrets": {
                    "$ref": "#/definitions/Secrets"
                },
                "configMaps": {
                    "$ref": "#/definitions/ConfigMaps"
                },
                "env": {
                    "type": "null"
                },
                "envFrom": {
                    "type": "array",
                    "items": {}
                },
                "priorityClassName": {
                    "type": "null"
                },
                "runtimeClassName": {
                    "type": "null"
                },
                "schedulerName": {
                    "type": "null"
                },
                "hostname": {
                    "type": "null"
                },
                "hostNetwork": {
                    "type": "boolean"
                },
                "dnsPolicy": {
                    "type": "null"
                },
                "dnsConfig": {
                    "$ref": "#/definitions/Affinity"
                },
                "enableServiceLinks": {
                    "type": "boolean"
                },
                "podSecurityContext": {
                    "$ref": "#/definitions/Affinity"
                },
                "securityContext": {
                    "$ref": "#/definitions/Affinity"
                },
                "lifecycle": {
                    "$ref": "#/definitions/Affinity"
                },
                "initContainers": {
                    "$ref": "#/definitions/Affinity"
                },
                "sidecars": {
                    "$ref": "#/definitions/Affinity"
                },
                "probes": {
                    "$ref": "#/definitions/Probes"
                },
                "termination": {
                    "$ref": "#/definitions/Termination"
                },
                "service": {
                    "$ref": "#/definitions/ValuesService"
                },
                "serviceMonitor": {
                    "$ref": "#/definitions/ServiceMonitor"
                },
                "ingress": {
                    "$ref": "#/definitions/ValuesIngress"
                },
                "route": {
                    "$ref": "#/definitions/Route"
                },
                "persistence": {
                    "$ref": "#/definitions/Persistence"
                },
                "volumeClaimTemplates": {
                    "type": "array",
                    "items": {}
                },
                "nodeSelector": {
                    "$ref": "#/definitions/Affinity"
                },
                "affinity": {
                    "$ref": "#/definitions/Affinity"
                },
                "topologySpreadConstraints": {
                    "type": "array",
                    "items": {}
                },
                "tolerations": {
                    "type": "array",
                    "items": {}
                },
                "hostAliases": {
                    "type": "array",
                    "items": {}
                },
                "resources": {
                    "$ref": "#/definitions/Affinity"
                },
                "addons": {
                    "$ref": "#/definitions/Addons"
                }
            },
            "required": [
                "addons",
                "affinity",
                "args",
                "automountServiceAccountToken",
                "command",
                "configMaps",
                "controller",
                "dnsConfig",
                "dnsPolicy",
                "enableServiceLinks",
                "env",
                "envFrom",
                "global",
                "hostAliases",
                "hostNetwork",
                "hostname",
                "image",
                "imagePullSecrets",
                "ingress",
                "initContainers",
                "lifecycle",
                "nodeSelector",
                "persistence",
                "podAnnotations",
                "podLabels",
                "podSecurityContext",
                "priorityClassName",
                "probes",
                "resources",
                "route",
                "runtimeClassName",
                "schedulerName",
                "secrets",
                "securityContext",
                "service",
                "serviceAccount",
                "serviceMonitor",
                "sidecars",
                "termination",
                "tolerations",
                "topologySpreadConstraints",
                "volumeClaimTemplates"
            ],
            "title": "Values"
        },
        "Addons": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "vpn": {
                    "$ref": "#/definitions/VPN"
                },
                "codeserver": {
                    "$ref": "#/definitions/AddonsCodeserver"
                },
                "netshoot": {
                    "$ref": "#/definitions/Netshoot"
                }
            },
            "required": [
                "codeserver",
                "netshoot",
                "vpn"
            ],
            "title": "Addons"
        },
        "AddonsCodeserver": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "image": {
                    "$ref": "#/definitions/Image"
                },
                "env": {
                    "$ref": "#/definitions/Affinity"
                },
                "args": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "volumeMounts": {
                    "type": "array",
                    "items": {}
                },
                "workingDir": {
                    "type": "string"
                },
                "git": {
                    "$ref": "#/definitions/Git"
                },
                "service": {
                    "$ref": "#/definitions/CodeserverService"
                },
                "ingress": {
                    "$ref": "#/definitions/CodeserverIngress"
                },
                "securityContext": {
                    "$ref": "#/definitions/CodeserverSecurityContext"
                }
            },
            "required": [
                "args",
                "enabled",
                "env",
                "git",
                "image",
                "ingress",
                "securityContext",
                "service",
                "volumeMounts",
                "workingDir"
            ],
            "title": "AddonsCodeserver"
        },
        "Affinity": {
            "type": "object",
            "additionalProperties": false,
            "title": "Affinity"
        },
        "Git": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "deployKey": {
                    "type": "string"
                },
                "deployKeyBase64": {
                    "type": "string"
                },
                "deployKeySecret": {
                    "type": "string"
                }
            },
            "required": [
                "deployKey",
                "deployKeyBase64",
                "deployKeySecret"
            ],
            "title": "Git"
        },
        "Image": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "repository": {
                    "anyOf": [
                        {
                            "type": "null"
                        },
                        {
                            "type": "string"
                        }
                    ]
                },
                "tag": {
                    "anyOf": [
                        {
                            "type": "null"
                        },
                        {
                            "type": "string"
                        }
                    ]
                },
                "pullPolicy": {
                    "anyOf": [
                        {
                            "type": "null"
                        },
                        {
                            "type": "string"
                        }
                    ]
                }
            },
            "required": [
                "pullPolicy",
                "repository",
                "tag"
            ],
            "title": "Image"
        },
        "CodeserverIngress": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "ingressClassName": {
                    "type": "null"
                },
                "hosts": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/IngressHost"
                    }
                },
                "tls": {
                    "type": "array",
                    "items": {}
                }
            },
            "required": [
                "annotations",
                "enabled",
                "hosts",
                "ingressClassName",
                "labels",
                "tls"
            ],
            "title": "CodeserverIngress"
        },
        "IngressHost": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "host": {
                    "type": "string"
                },
                "paths": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/PurplePath"
                    }
                }
            },
            "required": [
                "host",
                "paths"
            ],
            "title": "IngressHost"
        },
        "PurplePath": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "path": {
                    "type": "string"
                },
                "pathType": {
                    "type": "string"
                }
            },
            "required": [
                "path",
                "pathType"
            ],
            "title": "PurplePath"
        },
        "CodeserverSecurityContext": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "runAsUser": {
                    "type": "integer"
                }
            },
            "required": [
                "runAsUser"
            ],
            "title": "CodeserverSecurityContext"
        },
        "CodeserverService": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "ports": {
                    "$ref": "#/definitions/ServicePorts"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                }
            },
            "required": [
                "annotations",
                "enabled",
                "labels",
                "ports",
                "type"
            ],
            "title": "CodeserverService"
        },
        "ServicePorts": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "codeserver": {
                    "$ref": "#/definitions/PortsCodeserver"
                }
            },
            "required": [
                "codeserver"
            ],
            "title": "ServicePorts"
        },
        "PortsCodeserver": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "port": {
                    "type": "integer"
                },
                "enabled": {
                    "type": "boolean"
                },
                "protocol": {
                    "type": "string"
                },
                "targetPort": {
                    "type": "integer"
                }
            },
            "required": [
                "enabled",
                "port",
                "protocol",
                "targetPort"
            ],
            "title": "PortsCodeserver"
        },
        "Netshoot": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "image": {
                    "$ref": "#/definitions/Image"
                },
                "env": {
                    "$ref": "#/definitions/Affinity"
                },
                "securityContext": {
                    "$ref": "#/definitions/NetshootSecurityContext"
                }
            },
            "required": [
                "enabled",
                "env",
                "image",
                "securityContext"
            ],
            "title": "Netshoot"
        },
        "NetshootSecurityContext": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "capabilities": {
                    "$ref": "#/definitions/Capabilities"
                }
            },
            "required": [
                "capabilities"
            ],
            "title": "NetshootSecurityContext"
        },
        "Capabilities": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "add": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            },
            "required": [
                "add"
            ],
            "title": "Capabilities"
        },
        "VPN": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "gluetun": {
                    "$ref": "#/definitions/Gluetun"
                },
                "securityContext": {
                    "$ref": "#/definitions/NetshootSecurityContext"
                },
                "env": {
                    "$ref": "#/definitions/Affinity"
                },
                "args": {
                    "type": "array",
                    "items": {}
                },
                "configFile": {
                    "type": "null"
                },
                "configFileSecret": {
                    "type": "null"
                },
                "scripts": {
                    "$ref": "#/definitions/Scripts"
                },
                "additionalVolumeMounts": {
                    "type": "array",
                    "items": {}
                },
                "livenessProbe": {
                    "$ref": "#/definitions/Affinity"
                },
                "networkPolicy": {
                    "$ref": "#/definitions/NetworkPolicy"
                }
            },
            "required": [
                "additionalVolumeMounts",
                "args",
                "configFile",
                "configFileSecret",
                "enabled",
                "env",
                "gluetun",
                "livenessProbe",
                "networkPolicy",
                "scripts",
                "securityContext",
                "type"
            ],
            "title": "VPN"
        },
        "Gluetun": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "image": {
                    "$ref": "#/definitions/Image"
                }
            },
            "required": [
                "image"
            ],
            "title": "Gluetun"
        },
        "NetworkPolicy": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "podSelectorLabels": {
                    "$ref": "#/definitions/Affinity"
                },
                "egress": {
                    "type": "null"
                }
            },
            "required": [
                "annotations",
                "egress",
                "enabled",
                "labels",
                "podSelectorLabels"
            ],
            "title": "NetworkPolicy"
        },
        "Scripts": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "up": {
                    "type": "null"
                },
                "down": {
                    "type": "null"
                }
            },
            "required": [
                "down",
                "up"
            ],
            "title": "Scripts"
        },
        "ConfigMaps": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "config": {
                    "$ref": "#/definitions/SecretClass"
                }
            },
            "required": [
                "config"
            ],
            "title": "ConfigMaps"
        },
        "SecretClass": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "data": {
                    "$ref": "#/definitions/Affinity"
                },
                "stringData": {
                    "$ref": "#/definitions/Affinity"
                }
            },
            "required": [
                "annotations",
                "enabled",
                "labels"
            ],
            "title": "SecretClass"
        },
        "Controller": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "replicas": {
                    "type": "integer"
                },
                "strategy": {
                    "type": "null"
                },
                "rollingUpdate": {
                    "$ref": "#/definitions/RollingUpdate"
                },
                "revisionHistoryLimit": {
                    "type": "integer"
                },
                "podManagementPolicy": {
                    "type": "null"
                },
                "restartPolicy": {
                    "type": "null"
                },
                "cronjob": {
                    "$ref": "#/definitions/Cronjob"
                }
            },
            "required": [
                "annotations",
                "cronjob",
                "enabled",
                "labels",
                "podManagementPolicy",
                "replicas",
                "restartPolicy",
                "revisionHistoryLimit",
                "rollingUpdate",
                "strategy",
                "type"
            ],
            "title": "Controller"
        },
        "Cronjob": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "concurrencyPolicy": {
                    "type": "string"
                },
                "schedule": {
                    "type": "string"
                },
                "startingDeadlineSeconds": {
                    "type": "integer"
                },
                "successfulJobsHistory": {
                    "type": "integer"
                },
                "failedJobsHistory": {
                    "type": "integer"
                },
                "ttlSecondsAfterFinished": {
                    "type": "null"
                }
            },
            "required": [
                "concurrencyPolicy",
                "failedJobsHistory",
                "schedule",
                "startingDeadlineSeconds",
                "successfulJobsHistory",
                "ttlSecondsAfterFinished"
            ],
            "title": "Cronjob"
        },
        "RollingUpdate": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "unavailable": {
                    "type": "null"
                },
                "surge": {
                    "type": "null"
                },
                "partition": {
                    "type": "null"
                }
            },
            "required": [
                "partition",
                "surge",
                "unavailable"
            ],
            "title": "RollingUpdate"
        },
        "Global": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "nameOverride": {
                    "type": "null"
                },
                "fullnameOverride": {
                    "type": "null"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                }
            },
            "required": [
                "annotations",
                "fullnameOverride",
                "labels",
                "nameOverride"
            ],
            "title": "Global"
        },
        "ValuesIngress": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "main": {
                    "$ref": "#/definitions/IngressMain"
                }
            },
            "required": [
                "main"
            ],
            "title": "ValuesIngress"
        },
        "IngressMain": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "primary": {
                    "type": "boolean"
                },
                "nameOverride": {
                    "type": "null"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "ingressClassName": {
                    "type": "null"
                },
                "hosts": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/MainHost"
                    }
                },
                "tls": {
                    "type": "array",
                    "items": {}
                }
            },
            "required": [
                "annotations",
                "enabled",
                "hosts",
                "ingressClassName",
                "labels",
                "nameOverride",
                "primary",
                "tls"
            ],
            "title": "IngressMain"
        },
        "MainHost": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "host": {
                    "type": "string"
                },
                "paths": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/FluffyPath"
                    }
                }
            },
            "required": [
                "host",
                "paths"
            ],
            "title": "MainHost"
        },
        "FluffyPath": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "path": {
                    "type": "string"
                },
                "pathType": {
                    "type": "string"
                },
                "service": {
                    "$ref": "#/definitions/PathService"
                }
            },
            "required": [
                "path",
                "pathType",
                "service"
            ],
            "title": "FluffyPath"
        },
        "PathService": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "name": {
                    "type": "null"
                },
                "port": {
                    "type": "null"
                }
            },
            "required": [
                "name",
                "port"
            ],
            "title": "PathService"
        },
        "Persistence": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "config": {
                    "$ref": "#/definitions/PersistenceConfig"
                },
                "shared": {
                    "$ref": "#/definitions/Shared"
                }
            },
            "required": [
                "config",
                "shared"
            ],
            "title": "Persistence"
        },
        "PersistenceConfig": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "mountPath": {
                    "type": "null"
                },
                "readOnly": {
                    "type": "boolean"
                },
                "nameOverride": {
                    "type": "null"
                },
                "storageClass": {
                    "type": "null"
                },
                "existingClaim": {
                    "type": "null"
                },
                "subPath": {
                    "type": "null"
                },
                "accessMode": {
                    "type": "string"
                },
                "size": {
                    "type": "string"
                },
                "retain": {
                    "type": "boolean"
                }
            },
            "required": [
                "accessMode",
                "enabled",
                "existingClaim",
                "mountPath",
                "nameOverride",
                "readOnly",
                "retain",
                "size",
                "storageClass",
                "subPath",
                "type"
            ],
            "title": "PersistenceConfig"
        },
        "Shared": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "mountPath": {
                    "type": "string"
                },
                "medium": {
                    "type": "null"
                },
                "sizeLimit": {
                    "type": "null"
                }
            },
            "required": [
                "enabled",
                "medium",
                "mountPath",
                "sizeLimit",
                "type"
            ],
            "title": "Shared"
        },
        "Probes": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "liveness": {
                    "$ref": "#/definitions/Liveness"
                },
                "readiness": {
                    "$ref": "#/definitions/Liveness"
                },
                "startup": {
                    "$ref": "#/definitions/Liveness"
                }
            },
            "required": [
                "liveness",
                "readiness",
                "startup"
            ],
            "title": "Probes"
        },
        "Liveness": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "custom": {
                    "type": "boolean"
                },
                "type": {
                    "type": "string"
                },
                "spec": {
                    "$ref": "#/definitions/LivenessSpec"
                }
            },
            "required": [
                "custom",
                "enabled",
                "spec",
                "type"
            ],
            "title": "Liveness"
        },
        "LivenessSpec": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "initialDelaySeconds": {
                    "type": "integer"
                },
                "periodSeconds": {
                    "type": "integer"
                },
                "timeoutSeconds": {
                    "type": "integer"
                },
                "failureThreshold": {
                    "type": "integer"
                }
            },
            "required": [
                "failureThreshold",
                "initialDelaySeconds",
                "periodSeconds",
                "timeoutSeconds"
            ],
            "title": "LivenessSpec"
        },
        "Route": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "main": {
                    "$ref": "#/definitions/RouteMain"
                }
            },
            "required": [
                "main"
            ],
            "title": "Route"
        },
        "RouteMain": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "kind": {
                    "type": "string"
                },
                "nameOverride": {
                    "type": "null"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "parentRefs": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/ParentRef"
                    }
                },
                "hostnames": {
                    "type": "array",
                    "items": {}
                },
                "rules": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/Rule"
                    }
                }
            },
            "required": [
                "annotations",
                "enabled",
                "hostnames",
                "kind",
                "labels",
                "nameOverride",
                "parentRefs",
                "rules"
            ],
            "title": "RouteMain"
        },
        "ParentRef": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "group": {
                    "type": "string"
                },
                "kind": {
                    "type": "string"
                },
                "name": {
                    "type": "null"
                },
                "namespace": {
                    "type": "null"
                },
                "sectionName": {
                    "type": "null"
                }
            },
            "required": [
                "group",
                "kind",
                "name",
                "namespace",
                "sectionName"
            ],
            "title": "ParentRef"
        },
        "Rule": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "backendRefs": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/BackendRef"
                    }
                },
                "matches": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/Match"
                    }
                }
            },
            "required": [
                "backendRefs",
                "matches"
            ],
            "title": "Rule"
        },
        "BackendRef": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "group": {
                    "type": "string"
                },
                "kind": {
                    "type": "string"
                },
                "name": {
                    "type": "null"
                },
                "namespace": {
                    "type": "null"
                },
                "port": {
                    "type": "null"
                },
                "weight": {
                    "type": "integer"
                }
            },
            "required": [
                "group",
                "kind",
                "name",
                "namespace",
                "port",
                "weight"
            ],
            "title": "BackendRef"
        },
        "Match": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "path": {
                    "$ref": "#/definitions/MatchPath"
                }
            },
            "required": [
                "path"
            ],
            "title": "Match"
        },
        "MatchPath": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "type": {
                    "type": "string"
                },
                "value": {
                    "type": "string"
                }
            },
            "required": [
                "type",
                "value"
            ],
            "title": "MatchPath"
        },
        "Secrets": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "secret": {
                    "$ref": "#/definitions/SecretClass"
                }
            },
            "required": [
                "secret"
            ],
            "title": "Secrets"
        },
        "ValuesService": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "main": {
                    "$ref": "#/definitions/ServiceMain"
                }
            },
            "required": [
                "main"
            ],
            "title": "ValuesService"
        },
        "ServiceMain": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "primary": {
                    "type": "boolean"
                },
                "nameOverride": {
                    "type": "null"
                },
                "type": {
                    "type": "string"
                },
                "externalTrafficPolicy": {
                    "type": "null"
                },
                "ipFamilyPolicy": {
                    "type": "null"
                },
                "ipFamilies": {
                    "type": "array",
                    "items": {}
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "ports": {
                    "$ref": "#/definitions/MainPorts"
                }
            },
            "required": [
                "annotations",
                "enabled",
                "externalTrafficPolicy",
                "ipFamilies",
                "ipFamilyPolicy",
                "labels",
                "nameOverride",
                "ports",
                "primary",
                "type"
            ],
            "title": "ServiceMain"
        },
        "MainPorts": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "http": {
                    "$ref": "#/definitions/HTTP"
                }
            },
            "required": [
                "http"
            ],
            "title": "MainPorts"
        },
        "HTTP": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "primary": {
                    "type": "boolean"
                },
                "port": {
                    "type": "null"
                },
                "protocol": {
                    "type": "string"
                },
                "targetPort": {
                    "type": "null"
                },
                "nodePort": {
                    "type": "null"
                },
                "extraSelectorLabels": {
                    "$ref": "#/definitions/Affinity"
                }
            },
            "required": [
                "enabled",
                "extraSelectorLabels",
                "nodePort",
                "port",
                "primary",
                "protocol",
                "targetPort"
            ],
            "title": "HTTP"
        },
        "ServiceAccount": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "create": {
                    "type": "boolean"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "name": {
                    "type": "string"
                }
            },
            "required": [
                "annotations",
                "create",
                "name"
            ],
            "title": "ServiceAccount"
        },
        "ServiceMonitor": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "main": {
                    "$ref": "#/definitions/ServiceMonitorMain"
                }
            },
            "required": [
                "main"
            ],
            "title": "ServiceMonitor"
        },
        "ServiceMonitorMain": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "nameOverride": {
                    "type": "null"
                },
                "annotations": {
                    "$ref": "#/definitions/Affinity"
                },
                "labels": {
                    "$ref": "#/definitions/Affinity"
                },
                "selector": {
                    "$ref": "#/definitions/Affinity"
                },
                "serviceName": {
                    "type": "string"
                },
                "endpoints": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/Endpoint"
                    }
                }
            },
            "required": [
                "annotations",
                "enabled",
                "endpoints",
                "labels",
                "nameOverride",
                "selector",
                "serviceName"
            ],
            "title": "ServiceMonitorMain"
        },
        "Endpoint": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "port": {
                    "type": "string"
                },
                "scheme": {
                    "type": "string"
                },
                "path": {
                    "type": "string"
                },
                "interval": {
                    "type": "string"
                },
                "scrapeTimeout": {
                    "type": "string"
                }
            },
            "required": [
                "interval",
                "path",
                "port",
                "scheme",
                "scrapeTimeout"
            ],
            "title": "Endpoint"
        },
        "Termination": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "messagePath": {
                    "type": "null"
                },
                "messagePolicy": {
                    "type": "null"
                },
                "gracePeriodSeconds": {
                    "type": "null"
                }
            },
            "required": [
                "gracePeriodSeconds",
                "messagePath",
                "messagePolicy"
            ],
            "title": "Termination"
        }
    }
}

This is not perfect as the values of each property aren't specified but is better than nothing as seen in the screenshot:
imagen

I think that having this schema generated as part of the chart will make upgrades easier

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.