diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 23866d6c4..7dee34fda 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -21,7 +21,8 @@ concurrency: cancel-in-progress: true env: - IMAGE_NAME: ghcr.io/${{ github.repository }}-ci:PR${{ github.event.number }} + REPOSITORY_NAME: ghcr.io/${{ github.repository }}-ci + TAG_NAME: PR${{ github.event.number }} VCLUSTER_SUFFIX: vcluster VCLUSTER_NAME: vcluster VCLUSTER_NAMESPACE: vcluster @@ -53,8 +54,8 @@ jobs: run: | set -x TELEMETRY_PRIVATE_KEY="" goreleaser build --single-target --snapshot --id vcluster --clean --output ./vcluster - docker build -t ${{ env.IMAGE_NAME }} -f Dockerfile.release --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux . - docker save -o vcluster_syncer ${{ env.IMAGE_NAME }} + docker build -t "${{ env.REPOSITORY_NAME }}:${{ env.TAG_NAME }}" -f Dockerfile.release --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux . + docker save -o vcluster_syncer "${{ env.REPOSITORY_NAME }}:${{ env.TAG_NAME }}" - name: Upload syncer image to artifact uses: actions/upload-artifact@v4 with: @@ -139,6 +140,7 @@ jobs: name: e2e-binaries path: ./test/*/*.test retention-days: 7 + download-latest-cli: name: Execute test suites runs-on: ubuntu-latest @@ -153,7 +155,6 @@ jobs: path: ./vcluster-current retention-days: 7 - upgrade-test: name: test if we can upgrade from older version needs: @@ -192,6 +193,7 @@ jobs: with: name: vcluster path: vcluster-dev + - name: Download current cli uses: actions/download-artifact@v4 with: @@ -201,6 +203,7 @@ jobs: uses: actions/download-artifact@v4 with: name: vcluster_syncer + - name: install sed run: | sudo apt-get install -y sed @@ -217,22 +220,24 @@ jobs: --distro=${{ matrix.distribution }} kubectl wait --for=condition=ready pod -l app=${{ env.VCLUSTER_SUFFIX }} -n ${{ env.VCLUSTER_NAMESPACE }} --timeout=300s + - name: upgrade with the dev cli run: | chmod +x ./vcluster-dev/vcluster set -x - sed -i "s|REPLACE_IMAGE_NAME|${{ env.IMAGE_NAME }}|g" test/commonValues.yaml + sed -i "s|REPLACE_REPOSITORY_NAME|${{ env.REPOSITORY_NAME }}|g" test/commonValues.yaml + sed -i "s|REPLACE_TAG_NAME|${{ env.TAG_NAME }}|g" test/commonValues.yaml ./vcluster-dev/vcluster create vcluster --distro=${{ matrix.distribution }} \ --connect=false \ --upgrade \ + --local-chart-dir ./chart \ -f ./test/commonValues.yaml sleep 20 kubectl wait --for=condition=ready pod -l app=${{ env.VCLUSTER_SUFFIX }} -n ${{ env.VCLUSTER_NAMESPACE }} --timeout=300s - e2e-tests: name: Execute test suites needs: @@ -340,7 +345,8 @@ jobs: sudo apt-get install -y sed - sed -i "s|REPLACE_IMAGE_NAME|${{ env.IMAGE_NAME }}|g" ${{ matrix.test-suite-path }}/../commonValues.yaml + sed -i "s|REPLACE_REPOSITORY_NAME|${{ env.REPOSITORY_NAME }}|g" ${{ matrix.test-suite-path }}/../commonValues.yaml + sed -i "s|REPLACE_TAG_NAME|${{ env.TAG_NAME }}|g" ${{ matrix.test-suite-path }}/../commonValues.yaml kind load image-archive vcluster_syncer @@ -351,7 +357,7 @@ jobs: --debug \ --connect=false \ --distro=${{ matrix.distribution }} \ - --local-chart-dir ./charts/${{ matrix.distribution }} \ + --local-chart-dir ./chart \ -f ./test/commonValues.yaml \ $haValues \ -f ${{ matrix.test-suite-path }}/values.yaml \ diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 46ab65399..e16a4441e 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -25,7 +25,7 @@ jobs: with: go-version-file: ./go.mod cache: false - - name: Generate Embedded Helm Charts + - name: Generate Embedded Helm Chart run: | go generate ./... - name: Run golangci-lint diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1fef6ef1b..a01c01305 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -90,10 +90,7 @@ jobs: RELEASE_VERSION=$(echo $GITHUB_REF | sed -nE 's!refs/tags/v!!p') helm plugin install https://github.com/chartmuseum/helm-push.git helm repo add chartmuseum $CHART_MUSEUM_URL --username $CHART_MUSEUM_USER --password $CHART_MUSEUM_PASSWORD - helm cm-push --force --version="$RELEASE_VERSION" --app-version="$RELEASE_VERSION" charts/k3s/ chartmuseum - helm cm-push --force --version="$RELEASE_VERSION" --app-version="$RELEASE_VERSION" charts/k0s/ chartmuseum - helm cm-push --force --version="$RELEASE_VERSION" --app-version="$RELEASE_VERSION" charts/k8s/ chartmuseum - helm cm-push --force --version="$RELEASE_VERSION" --app-version="$RELEASE_VERSION" charts/eks/ chartmuseum + helm cm-push --force --version="$RELEASE_VERSION" --app-version="$RELEASE_VERSION" chart chartmuseum env: CHART_MUSEUM_URL: "https://charts.loft.sh/" CHART_MUSEUM_USER: ${{ secrets.CHART_MUSEUM_USER }} diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 855ca83e5..32f02aedd 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -29,10 +29,7 @@ jobs: helm plugin install https://github.com/helm-unittest/helm-unittest - name: Run Helm Unit Tests run: | - helm unittest charts/eks -d - helm unittest charts/k3s -d - helm unittest charts/k0s -d - helm unittest charts/k8s -d + helm unittest chart go-unit-test: name: Execute all go tests diff --git a/.golangci.yml b/.golangci.yml index 466ad0b96..79637e252 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,6 +5,7 @@ linters: disable-all: true enable: - asasalint + - tagalign - asciicheck - bidichk - decorder @@ -31,7 +32,6 @@ linters: - revive - staticcheck - stylecheck - - tagalign - typecheck - unconvert - unused diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 186f2f4ea..404170443 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,6 +144,65 @@ Run: ./vcluster create v1 # create vcluster ``` +### Deploy vCluster from source + +#### 1. Check if you have the following software installed: + +* A running Kubernetes cluster v1.26 or newer and Kubectl installed, verify Kubernetes version via `kubectl version` +* Docker needs to be installed (e.g. docker-desktop, orbstack, rancher desktop etc.) +* Helm installed and verify via `helm version` +* Golang version 1.22 and verify via `go version` + +#### 2. Git clone the repository `git clone https://github.com/loft-sh/vcluster.git` + +#### 3. Build vCluster CLI first: +``` +sudo go build -mod vendor -o /usr/local/bin/vcluster cmd/vclusterctl/main.go +``` + +**If you already have vCluster CLI installed, please make sure to uninstall it first.** + +#### 4. Verify vCluster CLI was compiled correctly via `vcluster version`: + +``` +$ vcluster version +vcluster version 0.0.1 +``` + +#### 5. Build vCluster Container Image: + +``` +docker build . -t my-vcluster:0.0.1 +``` + +#### 5a. Optional: if using kind, you need to import the image into kind + +``` +kind load docker-image my-vcluster:0.0.1 +``` + +#### 6. Create vCluster with self-compiled vCluster CLI + +We can create a new `vcluster.yaml`: +```yaml +controlPlane: + statefulSet: + imagePullPolicy: Never + image: + repository: my-vcluster + tag: 0.0.1 +``` + +Then deploy the vCluster with the provided `vcluster.yaml`: +``` +vcluster create my-vcluster -n my-vcluster -f ./vcluster.yaml --local-chart-dir chart +``` + +Afterwards open a second terminal and use the vCluster: +``` +kubectl get ns +``` + ## Developing the hostpath-mapper component instead of syncer In case you need to develop the hostpath-mapper daemonset instead of the syncer, you can use the `dev-hostpath-mapper` profile in `devspace.yaml`. You can do this by running the following command: diff --git a/Dockerfile b/Dockerfile index fee81bbc1..178bda1ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ COPY vendor/ vendor/ COPY cmd/vcluster cmd/vcluster COPY cmd/vclusterctl cmd/vclusterctl COPY pkg/ pkg/ +COPY config/ config/ ENV GO111MODULE on ENV DEBUG true diff --git a/Justfile b/Justfile index b17cac5c6..b004cf760 100644 --- a/Justfile +++ b/Justfile @@ -91,7 +91,8 @@ e2e distribution="k3s" path="./test/e2e" multinamespace="false": create-kind && cp test/commonValues.yaml dist/commonValues.yaml - sed -i.bak "s|REPLACE_IMAGE_NAME|vcluster:e2e-latest|g" dist/commonValues.yaml + sed -i.bak "s|REPLACE_REPOSITORY_NAME|vcluster|g" dist/commonValues.yaml + sed -i.bak "s|REPLACE_TAG_NAME|e2e-latest|g" dist/commonValues.yaml rm dist/commonValues.yaml.bak sed -i.bak "s|kind-control-plane|vcluster-control-plane|g" dist/commonValues.yaml diff --git a/charts/k0s/.helmignore b/chart/.helmignore similarity index 100% rename from charts/k0s/.helmignore rename to chart/.helmignore diff --git a/charts/k3s/Chart.yaml b/chart/Chart.yaml similarity index 100% rename from charts/k3s/Chart.yaml rename to chart/Chart.yaml diff --git a/charts/k3s/README.md b/chart/README.md similarity index 100% rename from charts/k3s/README.md rename to chart/README.md diff --git a/chart/templates/_backingstore.tpl b/chart/templates/_backingstore.tpl new file mode 100644 index 000000000..870bef478 --- /dev/null +++ b/chart/templates/_backingstore.tpl @@ -0,0 +1,22 @@ +{{/* + is external etcd enabled? +*/}} +{{- define "vcluster.externalEtcd.enabled" -}} +{{- if and (eq (include "vcluster.distro" .) "k8s") (not .Values.controlPlane.backingStore.embeddedEtcd.enabled) -}} +{{- true -}} +{{- else if and (eq (include "vcluster.distro" .) "eks") (not .Values.controlPlane.backingStore.embeddedEtcd.enabled) -}} +{{- true -}} +{{- else if .Values.controlPlane.backingStore.externalEtcd.enabled -}} +{{- true -}} +{{- end -}} +{{- end -}} + +{{/* + migrate from external etcd? +*/}} +{{- define "vcluster.externalEtcd.migrate" -}} +{{- if and .Values.controlPlane.backingStore.embeddedEtcd.enabled .Values.controlPlane.backingStore.embeddedEtcd.migrateFromExternalEtcd -}} +{{- true -}} +{{- end -}} +{{- end -}} + diff --git a/charts/eks/templates/_coredns.tpl b/chart/templates/_coredns.tpl similarity index 56% rename from charts/eks/templates/_coredns.tpl rename to chart/templates/_coredns.tpl index f5b1aff93..dd7518ec9 100644 --- a/charts/eks/templates/_coredns.tpl +++ b/chart/templates/_coredns.tpl @@ -3,8 +3,8 @@ */}} {{- define "vcluster.corefile" -}} Corefile: |- - {{- if .Values.coredns.config }} -{{ .Values.coredns.config | indent 8 }} + {{- if .Values.controlPlane.coredns.overwriteConfig }} +{{ .Values.controlPlane.coredns.overwriteConfig | indent 8 }} {{- else }} .:1053 { errors @@ -12,20 +12,18 @@ Corefile: |- ready rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local kubernetes cluster.local in-addr.arpa ip6.arpa { - {{- if .Values.pro }} - {{- if .Values.coredns.integrated }} - kubeconfig /pki/admin.conf - {{- end }} + {{- if .Values.controlPlane.coredns.embedded }} + kubeconfig /data/vcluster/admin.conf {{- end }} pods insecure - {{- if .Values.fallbackHostDns }} + {{- if or .Values.networking.advanced.fallbackHostCluster (and .Values.controlPlane.coredns.embedded .Values.networking.resolveDNS) }} fallthrough cluster.local in-addr.arpa ip6.arpa {{- else }} fallthrough in-addr.arpa ip6.arpa {{- end }} } - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - vcluster {{ toYaml .Values.coredns.plugin.config | b64enc }} + {{- if and .Values.controlPlane.coredns.embedded .Values.networking.resolveDNS }} + vcluster {{- end }} hosts /etc/NodeHosts { ttl 60 @@ -33,10 +31,10 @@ Corefile: |- fallthrough } prometheus :9153 - {{- if .Values.fallbackHostDns }} + {{- if .Values.networking.advanced.fallbackHostCluster }} forward . {{`{{.HOST_CLUSTER_DNS}}`}} - {{- else if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} - forward . /etc/resolv.conf {{ .Values.coredns.fallback }} { + {{- else if .Values.policies.networkPolicy.enabled }} + forward . /etc/resolv.conf {{ .Values.policies.networkPolicy.fallbackDns }} { policy sequential } {{- else }} diff --git a/chart/templates/_distro.tpl b/chart/templates/_distro.tpl new file mode 100644 index 000000000..902d337a5 --- /dev/null +++ b/chart/templates/_distro.tpl @@ -0,0 +1,39 @@ +{{- define "vcluster.distro.env" -}} +{{- if and (eq (include "vcluster.distro" .) "k3s") .Values.controlPlane.distro.k3s.env -}} +{{ .Values.controlPlane.distro.k3s.env }} +{{- else if and (eq (include "vcluster.distro" .) "k8s") .Values.controlPlane.distro.k8s.env -}} +{{ .Values.controlPlane.distro.k8s.env }} +{{- else if and (eq (include "vcluster.distro" .) "k0s") .Values.controlPlane.distro.k0s.env -}} +{{ .Values.controlPlane.distro.k0s.env }} +{{- else if and (eq (include "vcluster.distro" .) "eks") .Values.controlPlane.distro.eks.env -}} +{{ .Values.controlPlane.distro.eks.env }} +{{- end -}} +{{- end -}} + +{{/* + vCluster Distro +*/}} +{{- define "vcluster.distro" -}} +{{- $distros := 0 -}} +{{- if .Values.controlPlane.distro.k3s.enabled -}} +k3s +{{- $distros = add1 $distros -}} +{{- end -}} +{{- if .Values.controlPlane.distro.k0s.enabled -}} +k0s +{{- $distros = add1 $distros -}} +{{- end -}} +{{- if .Values.controlPlane.distro.k8s.enabled -}} +k8s +{{- $distros = add1 $distros -}} +{{- end -}} +{{- if .Values.controlPlane.distro.eks.enabled -}} +eks +{{- $distros = add1 $distros -}} +{{- end -}} +{{- if eq $distros 0 -}} +k3s +{{- else if gt $distros 1 -}} +{{- fail "you can only enable one distro at the same time" -}} +{{- end -}} +{{- end -}} diff --git a/chart/templates/_helper.tpl b/chart/templates/_helper.tpl new file mode 100644 index 000000000..272f98912 --- /dev/null +++ b/chart/templates/_helper.tpl @@ -0,0 +1,7 @@ +{{- define "vcluster.controlPlane.image" -}} +{{- if .Values.controlPlane.statefulSet.image.tag -}} +{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.statefulSet.image.repository }}:{{ .Values.controlPlane.statefulSet.image.tag }} +{{- else -}} +{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.statefulSet.image.repository }}:{{ .Chart.Version }}-pro +{{- end -}} +{{- end -}} diff --git a/chart/templates/_init-containers.tpl b/chart/templates/_init-containers.tpl new file mode 100644 index 000000000..11a4f820b --- /dev/null +++ b/chart/templates/_init-containers.tpl @@ -0,0 +1,327 @@ +{{- define "vcluster.initContainers" -}} +{{- if eq (include "vcluster.distro" .) "k3s" -}} +{{ include "vcluster.k3s.initContainers" . }} +{{- else if eq (include "vcluster.distro" .) "k8s" -}} +{{ include "vcluster.k8s.initContainers" . }} +{{- else if eq (include "vcluster.distro" .) "k0s" -}} +{{ include "vcluster.k0s.initContainers" . }} +{{- else if eq (include "vcluster.distro" .) "eks" -}} +{{ include "vcluster.eks.initContainers" . }} +{{- end -}} +{{- end -}} + +{{- define "vcluster.eks.initContainers" -}} +{{- include "vcluster.oldPlugins.initContainers" . }} +{{- include "vcluster.plugins.initContainers" . }} +# this is needed because the k8s containers are distroless and thus we don't have any +# way of copying the binaries otherwise +- name: vcluster-copy + image: {{ include "vcluster.controlPlane.image" . | quote }} + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /bin/sh + args: + - -c + - "cp /vcluster /binaries/vcluster" + {{- if .Values.controlPlane.statefulSet.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.statefulSet.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.eks.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.eks.resources | indent 4 }} +{{- if not .Values.controlPlane.distro.eks.controllerManager.disabled }} +- name: kube-controller-manager + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.eks.controllerManager.image.repository }}:{{ .Values.controlPlane.distro.eks.controllerManager.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-controller-manager + - /binaries/kube-controller-manager + {{- if .Values.controlPlane.distro.eks.controllerManager.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.eks.controllerManager.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.eks.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.eks.resources | indent 4 }} +{{- end }} +{{- if .Values.controlPlane.advanced.virtualScheduler.enabled }} +- name: kube-scheduler-manager + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.eks.scheduler.image.repository }}:{{ .Values.controlPlane.distro.eks.scheduler.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-scheduler + - /binaries/kube-scheduler + {{- if .Values.controlPlane.distro.eks.scheduler.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.eks.scheduler.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.eks.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.eks.resources | indent 4 }} +{{- end }} +{{- if not .Values.controlPlane.distro.eks.apiServer.disabled }} +- name: kube-apiserver + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.eks.apiServer.image.repository }}:{{ .Values.controlPlane.distro.eks.apiServer.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-apiserver + - /binaries/kube-apiserver + {{- if .Values.controlPlane.distro.eks.apiServer.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.eks.apiServer.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.eks.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.eks.resources | indent 4 }} +{{- end }} +{{- end -}} + +{{- define "vcluster.k8s.initContainers" -}} +{{- include "vcluster.oldPlugins.initContainers" . }} +{{- include "vcluster.plugins.initContainers" . }} +# this is needed because the k8s containers are distroless and thus we don't have any +# way of copying the binaries otherwise +- name: vcluster-copy + image: {{ include "vcluster.controlPlane.image" . | quote }} + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /bin/sh + args: + - -c + - "cp /vcluster /binaries/vcluster" + {{- if .Values.controlPlane.statefulSet.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.statefulSet.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k8s.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.k8s.resources | indent 4 }} +{{- if not .Values.controlPlane.distro.k8s.controllerManager.disabled }} +- name: kube-controller-manager + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.k8s.controllerManager.image.repository }}:{{ .Values.controlPlane.distro.k8s.controllerManager.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-controller-manager + - /binaries/kube-controller-manager + {{- if .Values.controlPlane.distro.k8s.controllerManager.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.k8s.controllerManager.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k8s.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.k8s.resources | indent 4 }} +{{- end }} +{{- if .Values.controlPlane.advanced.virtualScheduler.enabled }} +- name: kube-scheduler-manager + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.k8s.scheduler.image.repository }}:{{ .Values.controlPlane.distro.k8s.scheduler.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-scheduler + - /binaries/kube-scheduler + {{- if .Values.controlPlane.distro.k8s.scheduler.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.k8s.scheduler.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k8s.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.k8s.resources | indent 4 }} +{{- end }} +{{- if not .Values.controlPlane.distro.k8s.apiServer.disabled }} +- name: kube-apiserver + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.k8s.apiServer.image.repository }}:{{ .Values.controlPlane.distro.k8s.apiServer.image.tag }}" + volumeMounts: + - mountPath: /binaries + name: binaries + command: + - /binaries/vcluster + args: + - cp + - /usr/local/bin/kube-apiserver + - /binaries/kube-apiserver + {{- if .Values.controlPlane.distro.k8s.apiServer.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.k8s.apiServer.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k8s.securityContext | indent 4 }} + resources: +{{ toYaml .Values.controlPlane.distro.k8s.resources | indent 4 }} +{{- end }} +{{- end -}} + +{{- define "vcluster.k3s.initContainers" -}} +{{- include "vcluster.oldPlugins.initContainers" . }} +{{- include "vcluster.plugins.initContainers" . }} +- image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.k3s.image.repository }}:{{ .Values.controlPlane.distro.k3s.image.tag }}" + name: vcluster + command: + - /bin/sh + args: + - -c + - "cp /bin/k3s /binaries/k3s" + {{- if .Values.controlPlane.distro.k3s.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.k3s.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k3s.securityContext | indent 4 }} + volumeMounts: + - name: binaries + mountPath: /binaries + resources: +{{ toYaml .Values.controlPlane.distro.k3s.resources | indent 4 }} +{{- end -}} + +{{- define "vcluster.k0s.initContainers" -}} +{{- include "vcluster.oldPlugins.initContainers" . }} +{{- include "vcluster.plugins.initContainers" . }} +- image: {{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.distro.k0s.image.repository }}:{{ .Values.controlPlane.distro.k0s.image.tag }} + name: vcluster + command: + - /bin/sh + args: + - -c + - "cp /usr/local/bin/k0s /binaries/k0s" + {{- if .Values.controlPlane.distro.k0s.imagePullPolicy }} + imagePullPolicy: {{ .Values.controlPlane.distro.k0s.imagePullPolicy }} + {{- end }} + securityContext: +{{ toYaml .Values.controlPlane.distro.k0s.securityContext | indent 4 }} + volumeMounts: + - name: binaries + mountPath: /binaries + resources: +{{ toYaml .Values.controlPlane.distro.k0s.resources | indent 4 }} +{{- end -}} + +{{/* + Plugin init container definition +*/}} +{{- define "vcluster.plugins.initContainers" -}} +{{- range $key, $container := .Values.plugins }} +{{- if not $container.image }} +{{- continue }} +{{- end }} +- image: {{ $.Values.controlPlane.advanced.defaultImageRegistry }}{{ $container.image }} + {{- if $container.name }} + name: {{ $container.name | quote }} + {{- else }} + name: {{ $key | quote }} + {{- end }} + {{- if $container.imagePullPolicy }} + imagePullPolicy: {{ $container.imagePullPolicy }} + {{- end }} + {{- if or $container.command $container.args }} + {{- if $container.command }} + command: + {{- range $commandIndex, $command := $container.command }} + - {{ $command | quote }} + {{- end }} + {{- end }} + {{- if $container.args }} + args: + {{- range $argIndex, $arg := $container.args }} + - {{ $arg | quote }} + {{- end }} + {{- end }} + {{- else }} + command: ["sh"] + args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] + {{- end }} + {{- if $container.securityContext }} + securityContext: +{{ toYaml $container.securityContext | indent 4 }} + {{- end }} + {{- if $container.volumeMounts }} + volumeMounts: +{{ toYaml $container.volumeMounts | indent 4 }} + {{- else }} + volumeMounts: + - mountPath: /plugins + name: plugins + {{- end }} + {{- if $container.resources }} + resources: +{{ toYaml $container.resources | indent 4 }} + {{- end }} +{{- end }} +{{- end -}} + +{{/* + Old Plugin init container definition +*/}} +{{- define "vcluster.oldPlugins.initContainers" -}} +{{- range $key, $container := .Values.plugin }} +{{- if or (ne $container.version "v2") (not $container.image) -}} +{{- continue -}} +{{- end -}} +- image: {{ $.Values.controlPlane.advanced.defaultImageRegistry }}{{ $container.image }} + {{- if $container.name }} + name: {{ $container.name | quote }} + {{- else }} + name: {{ $key | quote }} + {{- end }} + {{- if $container.imagePullPolicy }} + imagePullPolicy: {{ $container.imagePullPolicy }} + {{- end }} + {{- if or $container.command $container.args }} + {{- if $container.command }} + command: + {{- range $commandIndex, $command := $container.command }} + - {{ $command | quote }} + {{- end }} + {{- end }} + {{- if $container.args }} + args: + {{- range $argIndex, $arg := $container.args }} + - {{ $arg | quote }} + {{- end }} + {{- end }} + {{- else }} + command: ["sh"] + args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] + {{- end }} + securityContext: +{{ toYaml $container.securityContext | indent 4 }} + {{- if $container.volumeMounts }} + volumeMounts: +{{ toYaml $container.volumeMounts | indent 4 }} + {{- else }} + volumeMounts: + - mountPath: /plugins + name: plugins + {{- end }} + {{- if $container.resources }} + resources: +{{ toYaml $container.resources | indent 4 }} + {{- end }} +{{- end }} +{{- end -}} diff --git a/chart/templates/_persistence.tpl b/chart/templates/_persistence.tpl new file mode 100644 index 000000000..b8c33ddcc --- /dev/null +++ b/chart/templates/_persistence.tpl @@ -0,0 +1,39 @@ +{{/* + ControlPlane Kind +*/}} +{{- define "vcluster.kind" -}} +{{ if include "vcluster.persistence.volumeClaim.enabled" . }}StatefulSet{{ else }}Deployment{{ end }} +{{- end -}} + +{{/* + StatefulSet Persistence Options +*/}} +{{- define "vcluster.persistence" -}} +{{- if .Values.controlPlane.statefulSet.persistence.volumeClaimTemplates }} +volumeClaimTemplates: +{{ toYaml .Values.controlPlane.statefulSet.persistence.volumeClaimTemplates | indent 2 }} +{{- else if include "vcluster.persistence.volumeClaim.enabled" . }} +volumeClaimTemplates: +- metadata: + name: data + spec: + accessModes: {{ .Values.controlPlane.statefulSet.persistence.volumeClaim.accessModes }} + {{- if .Values.controlPlane.statefulSet.persistence.volumeClaim.storageClass }} + storageClassName: {{ .Values.controlPlane.statefulSet.persistence.volumeClaim.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.controlPlane.statefulSet.persistence.volumeClaim.size }} +{{- end }} +{{- end -}} + +{{/* + is persistence enabled? +*/}} +{{- define "vcluster.persistence.volumeClaim.enabled" -}} +{{- if .Values.controlPlane.statefulSet.persistence.volumeClaimTemplates -}} +{{- true -}} +{{- else if and (not .Values.controlPlane.statefulSet.persistence.volumeClaim.disabled) (not (include "vcluster.externalEtcd.enabled" .)) -}} +{{- true -}} +{{- end -}} +{{- end -}} diff --git a/charts/k0s/templates/_plugin.tpl b/chart/templates/_plugin.tpl similarity index 55% rename from charts/k0s/templates/_plugin.tpl rename to chart/templates/_plugin.tpl index b1ecd2fa2..c2ca4ba2a 100644 --- a/charts/k0s/templates/_plugin.tpl +++ b/chart/templates/_plugin.tpl @@ -1,115 +1,51 @@ {{/* - Plugin config definition + Plugin volume mount definition */}} -{{- define "vcluster.plugins.config" -}} +{{- define "vcluster.plugins.volumeMounts" -}} {{- $pluginFound := false -}} {{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} -{{- $pluginFound = true -}} -{{- end }} -{{- if $pluginFound }} -- name: PLUGIN_CONFIG - value: |- -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} +{{- if or (ne $container.version "v2") (not $container.image) }} {{- continue }} {{- end }} - {{ $key }}: {{ toYaml $container.config | nindent 6 }} -{{- end }} +{{ $pluginFound = true }} +- mountPath: /plugins + name: plugins +{{- break }} {{- end }} -{{- end -}} - -{{/* - Plugin volume mount definition -*/}} -{{- define "vcluster.plugins.volumeMounts" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} +{{- if eq $pluginFound false }} +{{- range $key, $container := .Values.plugins }} +{{- if not $container.image }} {{- continue }} {{- end }} - mountPath: /plugins name: plugins {{- break }} {{- end }} +{{- end }} {{- end -}} {{/* Plugin volume definition */}} {{- define "vcluster.plugins.volumes" -}} +{{- $pluginFound := false -}} {{- range $key, $container := .Values.plugin }} {{- if or (ne $container.version "v2") (not $container.image) }} {{- continue }} {{- end }} +{{ $pluginFound = true }} - name: plugins emptyDir: {} {{- break }} {{- end }} -{{- end -}} - -{{/* - Plugin init container definition -*/}} -{{- define "vcluster.plugins.initContainers" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if or $container.command $container.args }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- else }} - command: ["sh"] - args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] - {{- end }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - {{- if $container.volumeMounts }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- else }} - volumeMounts: - - mountPath: /plugins - name: plugins - {{- end }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* - Extra Syncer Args for the legacy Plugins -*/}} -{{- define "vcluster.legacyPlugins.args" -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} +{{- if eq $pluginFound false }} +{{- range $key, $container := .Values.plugins }} +{{- if not $container.image }} {{- continue }} {{- end }} -{{- if not $container.optional }} -- --plugins={{ $key }} +- name: plugins + emptyDir: {} +{{- break }} {{- end }} {{- end }} {{- end -}} @@ -124,7 +60,7 @@ {{ continue }} {{- end }} {{- $counter = add1 $counter }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} +- image: {{ $.Values.controlPlane.advanced.defaultImageRegistry }}{{ $container.image }} {{- if $container.name }} name: {{ $container.name | quote }} {{- else }} @@ -184,5 +120,3 @@ {{- end }} {{- end }} {{- end }} - - diff --git a/chart/templates/_rbac.tpl b/chart/templates/_rbac.tpl new file mode 100644 index 000000000..50de084ee --- /dev/null +++ b/chart/templates/_rbac.tpl @@ -0,0 +1,138 @@ +{{- define "vcluster.clusterRoleName" -}} +{{- printf "vc-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "vcluster.clusterRoleNameMultinamespace" -}} +{{- printf "vc-mn-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* + Whether to create a cluster role or not +*/}} +{{- define "vcluster.createClusterRole" -}} +{{- if not .Values.rbac.clusterRole.disabled -}} +{{- if or + .Values.rbac.clusterRole.overwriteRules + (not (empty (include "vcluster.rbac.clusterRoleExtraRules" . ))) + (not (empty (include "vcluster.plugin.clusterRoleExtraRules" . ))) + (not (empty (include "vcluster.generic.clusterRoleExtraRules" . ))) + .Values.networking.replicateServices.fromHost + .Values.pro + .Values.sync.toHost.storageClasses.enabled + .Values.sync.toHost.persistentVolumes.enabled + .Values.sync.toHost.priorityClasses.enabled + .Values.sync.toHost.volumeSnapshots.enabled + .Values.controlPlane.advanced.virtualScheduler.enabled + .Values.sync.fromHost.ingressClasses.enabled + .Values.sync.fromHost.storageClasses.enabled + .Values.sync.fromHost.nodes.enabled + .Values.observability.metrics.proxy.nodes + .Values.experimental.multiNamespaceMode.enabled -}} +{{- true -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* + Role rules defined on global level +*/}} +{{- define "vcluster.rbac.roleExtraRules" -}} +{{- if .Values.rbac.role.extraRules }} +{{- range $ruleIndex, $rule := .Values.rbac.role.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* + Role rules defined by plugins +*/}} +{{- define "vcluster.plugin.roleExtraRules" -}} +{{- range $key, $container := .Values.plugin }} +{{- if $container.rbac }} +{{- if $container.rbac.role }} +{{- if $container.rbac.role.extraRules }} +{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- range $key, $container := .Values.plugins }} +{{- if $container.rbac }} +{{- if $container.rbac.role }} +{{- if $container.rbac.role.extraRules }} +{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* + Cluster role rules defined by plugins +*/}} +{{- define "vcluster.plugin.clusterRoleExtraRules" -}} +{{- range $key, $container := .Values.plugin }} +{{- if $container.rbac }} +{{- if $container.rbac.clusterRole }} +{{- if $container.rbac.clusterRole.extraRules }} +{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- range $key, $container := .Values.plugins }} +{{- if $container.rbac }} +{{- if $container.rbac.clusterRole }} +{{- if $container.rbac.clusterRole.extraRules }} +{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* + Role rules defined in generic syncer +*/}} +{{- define "vcluster.generic.roleExtraRules" -}} +{{- if .Values.experimental.genericSync.role }} +{{- if .Values.experimental.genericSync.role.extraRules }} +{{- range $ruleIndex, $rule := .Values.experimental.genericSync.role.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* + Cluster role rules defined in generic syncer +*/}} +{{- define "vcluster.generic.clusterRoleExtraRules" -}} +{{- if .Values.experimental.genericSync.clusterRole }} +{{- if .Values.experimental.genericSync.clusterRole.extraRules }} +{{- range $ruleIndex, $rule := .Values.experimental.genericSync.clusterRole.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* + Cluster Role rules defined on global level +*/}} +{{- define "vcluster.rbac.clusterRoleExtraRules" -}} +{{- if .Values.rbac.clusterRole.extraRules }} +{{- range $ruleIndex, $rule := .Values.rbac.clusterRole.extraRules }} +- {{ toJson $rule }} +{{- end }} +{{- end }} +{{- end -}} diff --git a/charts/k3s/templates/rbac/clusterrole.yaml b/chart/templates/clusterrole.yaml similarity index 57% rename from charts/k3s/templates/rbac/clusterrole.yaml rename to chart/templates/clusterrole.yaml index 984267cae..5ba424991 100644 --- a/charts/k3s/templates/rbac/clusterrole.yaml +++ b/chart/templates/clusterrole.yaml @@ -8,70 +8,60 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} {{- end }} rules: -{{- if .Values.pro }} + {{- if .Values.rbac.clusterRole.overwriteRules }} +{{ toYaml .Values.rbac.clusterRole.overwriteRules | indent 2 }} + {{- else }} + {{- if .Values.pro }} - apiGroups: ["cluster.loft.sh", "storage.loft.sh"] resources: ["features", "virtualclusters"] verbs: ["get", "list", "watch"] -{{- end }} - {{- if or .Values.pro .Values.sync.nodes.enabled }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["get", "watch", "list"] - - apiGroups: [""] - resources: [ "pods", "nodes/metrics", "nodes/stats"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.coredns.plugin.enabled }} - - apiGroups: [""] - resources: [ "pods"] - verbs: ["get", "watch", "list"] {{- end }} - {{- if and .Values.sync.nodes.enabled (or (not .Values.isolation.enabled) (and .Values.isolation.nodeProxyPermission.enabled .Values.isolation.enabled)) }} + {{- if or .Values.pro .Values.sync.fromHost.nodes.enabled }} - apiGroups: [""] - resources: ["nodes/proxy"] + resources: ["pods", "nodes", "nodes/status", "nodes/metrics", "nodes/stats", "nodes/proxy"] verbs: ["get", "watch", "list"] {{- end }} - {{- if and .Values.sync.nodes.enabled .Values.sync.nodes.syncNodeChanges }} + {{- if and .Values.sync.fromHost.nodes.enabled .Values.sync.fromHost.nodes.syncBackChanges }} - apiGroups: [""] resources: ["nodes", "nodes/status"] verbs: ["update", "patch"] {{- end }} - {{- if .Values.sync.persistentvolumes.enabled }} + {{- if .Values.controlPlane.advanced.virtualScheduler.enabled }} + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"] + verbs: ["get", "watch", "list"] + {{- end }} + {{- if .Values.sync.toHost.persistentVolumes.enabled }} - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] {{- end }} - {{- if .Values.sync.nodes.enableScheduler }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses","csinodes","csidrivers","csistoragecapacities"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if (include "vcluster.syncIngressclassesEnabled" . ) }} + {{- if .Values.sync.fromHost.ingressClasses.enabled }} - apiGroups: ["networking.k8s.io"] resources: ["ingressclasses"] verbs: ["get", "watch", "list"] {{- end }} - {{- if .Values.sync.storageclasses.enabled }} + {{- if .Values.sync.toHost.storageClasses.enabled }} - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] {{- end }} - {{- if or .Values.sync.hoststorageclasses.enabled (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") .Values.rbac.clusterRole.create }} + {{- if .Values.sync.fromHost.storageClasses.enabled }} - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "watch", "list"] {{- end }} - {{- if .Values.sync.priorityclasses.enabled }} + {{- if .Values.sync.toHost.priorityClasses.enabled }} - apiGroups: ["scheduling.k8s.io"] resources: ["priorityclasses"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} + {{- if .Values.sync.toHost.volumeSnapshots.enabled }} - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshotclasses"] verbs: ["get", "list", "watch"] @@ -79,20 +69,17 @@ rules: resources: ["volumesnapshotcontents"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if (not (empty (include "vcluster.serviceMapping.fromHost" . ))) }} + {{- if .Values.networking.replicateServices.fromHost }} - apiGroups: [""] resources: ["services", "endpoints"] verbs: ["get", "watch", "list"] {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} + {{- if .Values.experimental.multiNamespaceMode.enabled }} - apiGroups: [""] - resources: ["namespaces"] + resources: ["namespaces", "serviceaccounts"] verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.proxy.metricsServer.nodes.enabled }} + {{- if .Values.observability.metrics.proxy.nodes }} - apiGroups: ["metrics.k8s.io"] resources: ["nodes"] verbs: ["get", "list"] @@ -100,4 +87,5 @@ rules: {{- include "vcluster.plugin.clusterRoleExtraRules" . | indent 2 }} {{- include "vcluster.generic.clusterRoleExtraRules" . | indent 2 }} {{- include "vcluster.rbac.clusterRoleExtraRules" . | indent 2 }} + {{- end }} {{- end }} diff --git a/charts/eks/templates/rbac/clusterrolebinding.yaml b/chart/templates/clusterrolebinding.yaml similarity index 69% rename from charts/eks/templates/rbac/clusterrolebinding.yaml rename to chart/templates/clusterrolebinding.yaml index 0a5645d28..a1166ea68 100644 --- a/charts/eks/templates/rbac/clusterrolebinding.yaml +++ b/chart/templates/clusterrolebinding.yaml @@ -8,14 +8,14 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} {{- end }} subjects: - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} + {{- if .Values.controlPlane.advanced.serviceAccount.name }} + name: {{ .Values.controlPlane.advanced.serviceAccount.name }} {{- else }} name: vc-{{ .Release.Name }} {{- end }} diff --git a/chart/templates/config-secret.yaml b/chart/templates/config-secret.yaml new file mode 100644 index 000000000..eb8170e5e --- /dev/null +++ b/chart/templates/config-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "vc-config-{{ .Release.Name }}" + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} + annotations: +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} + {{- end }} +type: Opaque +data: + config.yaml: {{ .Values | toYaml | b64enc | quote }} diff --git a/charts/k0s/templates/coredns.yaml b/chart/templates/coredns-configmap.yaml similarity index 60% rename from charts/k0s/templates/coredns.yaml rename to chart/templates/coredns-configmap.yaml index cc25dd17f..5ee5fc335 100644 --- a/charts/k0s/templates/coredns.yaml +++ b/chart/templates/coredns-configmap.yaml @@ -1,18 +1,62 @@ -{{- if not .Values.headless }} -{{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} +{{- if and .Values.controlPlane.coredns.enabled (not .Values.experimental.isolatedControlPlane.headless) }} apiVersion: v1 kind: ConfigMap metadata: - name: {{ .Release.Name }}-coredns + name: vc-coredns-{{ .Release.Name }} namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} {{- end }} data: -{{- if .Values.coredns.manifests }} +{{- if .Values.controlPlane.coredns.overwriteManifests }} coredns.yaml: |- -{{ .Values.coredns.manifests | indent 4 }} +{{ .Values.controlPlane.coredns.overwriteManifests | indent 4 }} +{{- else if .Values.controlPlane.coredns.embedded }} +{{ include "vcluster.corefile" . | indent 2 }} + coredns.yaml: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: coredns + namespace: kube-system + data: + NodeHosts: "" + --- + apiVersion: v1 + kind: Service + metadata: + name: kube-dns + namespace: kube-system + annotations: + prometheus.io/port: "9153" + prometheus.io/scrape: "true" + {{- if .Values.controlPlane.coredns.service.annotations }} +{{ toYaml .Values.controlPlane.coredns.service.annotations | indent 8 }} + {{- end }} + labels: + k8s-app: kube-dns + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "CoreDNS" + {{- if .Values.controlPlane.coredns.service.labels }} +{{ toYaml .Values.controlPlane.coredns.service.labels | indent 8 }} + {{- end }} + spec: +{{ toYaml .Values.controlPlane.coredns.service.spec | indent 6 }} + {{- if not .Values.controlPlane.coredns.service.spec.ports }} + ports: + - name: dns + port: 53 + targetPort: 1053 + protocol: UDP + - name: dns-tcp + port: 53 + targetPort: 1053 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP + {{- end }} {{- else }} coredns.yaml: |- apiVersion: v1 @@ -77,11 +121,18 @@ data: metadata: name: coredns namespace: kube-system + {{- if .Values.controlPlane.coredns.deployment.annotations }} + annotations: +{{ toYaml .Values.controlPlane.coredns.deployment.annotations | indent 8 }} + {{- end }} labels: k8s-app: kube-dns kubernetes.io/name: "CoreDNS" + {{- if .Values.controlPlane.coredns.deployment.labels }} +{{ toYaml .Values.controlPlane.coredns.deployment.labels | indent 8 }} + {{- end }} spec: - replicas: {{ .Values.coredns.replicas }} + replicas: {{ .Values.controlPlane.coredns.deployment.replicas }} strategy: type: RollingUpdate rollingUpdate: @@ -91,22 +142,22 @@ data: k8s-app: kube-dns template: metadata: - {{- if .Values.coredns.podAnnotations }} + {{- if .Values.controlPlane.coredns.deployment.pods.annotations }} annotations: -{{ toYaml .Values.coredns.podAnnotations | indent 12 }} - {{- end }} +{{ toYaml .Values.controlPlane.coredns.deployment.pods.annotations | indent 12 }} + {{- end }} labels: k8s-app: kube-dns - {{- range $k, $v := .Values.coredns.podLabels }} - {{ $k }}: {{ $v | quote }} + {{- if .Values.controlPlane.coredns.deployment.pods.labels }} +{{ toYaml .Values.controlPlane.coredns.deployment.pods.labels | indent 12 }} {{- end }} spec: priorityClassName: "system-cluster-critical" serviceAccountName: coredns nodeSelector: kubernetes.io/os: linux - {{- if .Values.coredns.nodeSelector }} -{{ toYaml .Values.coredns.nodeSelector | indent 12 }} + {{- if .Values.controlPlane.coredns.deployment.nodeSelector }} +{{ toYaml .Values.controlPlane.coredns.deployment.nodeSelector | indent 12 }} {{- end }} topologySpreadConstraints: - maxSkew: 1 @@ -115,21 +166,23 @@ data: labelSelector: matchLabels: k8s-app: kube-dns - {{- if .Values.isolation.enabled }} + {{- if .Values.policies.podSecurityStandard }} securityContext: seccompProfile: type: RuntimeDefault {{- end }} containers: - name: coredns - {{- if .Values.coredns.image }} - image: {{ .Values.defaultImageRegistry }}{{ .Values.coredns.image }} + {{- if .Values.controlPlane.coredns.deployment.image }} + image: {{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ .Values.controlPlane.coredns.deployment.image }} {{- else }} image: {{`{{.IMAGE}}`}} {{- end }} imagePullPolicy: IfNotPresent + {{- if .Values.controlPlane.coredns.deployment.resources }} resources: -{{ toYaml .Values.coredns.resources | indent 16}} +{{ toYaml .Values.controlPlane.coredns.deployment.resources | indent 16 }} + {{- end }} args: [ "-conf", "/etc/coredns/Corefile" ] volumeMounts: - name: config-volume @@ -192,28 +245,23 @@ data: annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} + {{- if .Values.controlPlane.coredns.service.annotations }} +{{ toYaml .Values.controlPlane.coredns.service.annotations | indent 8 }} {{- end }} labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" + {{- if .Values.controlPlane.coredns.service.labels }} +{{ toYaml .Values.controlPlane.coredns.service.labels | indent 8 }} + {{- end }} spec: +{{ toYaml .Values.controlPlane.coredns.service.spec | indent 6 }} + {{- if not .Values.controlPlane.coredns.service.spec.selector }} selector: k8s-app: kube-dns - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} {{- end }} + {{- if not .Values.controlPlane.coredns.service.spec.ports }} ports: - name: dns port: 53 @@ -226,6 +274,6 @@ data: - name: metrics port: 9153 protocol: TCP -{{- end }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/eks/templates/etcd-statefulset-service.yaml b/chart/templates/etcd-headless-service.yaml similarity index 60% rename from charts/eks/templates/etcd-statefulset-service.yaml rename to chart/templates/etcd-headless-service.yaml index f037365bf..8b56d312d 100644 --- a/charts/eks/templates/etcd-statefulset-service.yaml +++ b/chart/templates/etcd-headless-service.yaml @@ -1,4 +1,6 @@ -{{- if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless ) (not .Values.etcd.disabled )) .Values.embeddedEtcd.migrateFromEtcd }} +{{- if not .Values.experimental.isolatedControlPlane.headless }} +{{- if or (include "vcluster.externalEtcd.enabled" .) (include "vcluster.externalEtcd.migrate" .) }} +{{- if .Values.controlPlane.backingStore.externalEtcd.headlessService.enabled }} apiVersion: v1 kind: Service metadata: @@ -9,7 +11,7 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.serviceAnnotations }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.controlPlane.backingStore.externalEtcd.headlessService.annotations }} {{- if $annotations }} annotations: {{ toYaml $annotations | indent 4 }} @@ -30,3 +32,5 @@ spec: app: vcluster-etcd release: "{{ .Release.Name }}" {{- end }} +{{- end }} +{{- end }} diff --git a/charts/k8s/templates/etcd-service.yaml b/chart/templates/etcd-service.yaml similarity index 59% rename from charts/k8s/templates/etcd-service.yaml rename to chart/templates/etcd-service.yaml index cbef5e804..a1b1c871b 100644 --- a/charts/k8s/templates/etcd-service.yaml +++ b/chart/templates/etcd-service.yaml @@ -1,4 +1,6 @@ -{{- if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless) (not .Values.etcd.disabled)) .Values.embeddedEtcd.migrateFromEtcd }} +{{- if not .Values.experimental.isolatedControlPlane.headless }} +{{- if or (include "vcluster.externalEtcd.enabled" .) (include "vcluster.externalEtcd.migrate" .) }} +{{- if .Values.controlPlane.backingStore.externalEtcd.service.enabled }} apiVersion: v1 kind: Service metadata: @@ -9,7 +11,7 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.serviceAnnotations }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.controlPlane.backingStore.externalEtcd.service.annotations }} {{- if $annotations }} annotations: {{ toYaml $annotations | indent 4 }} @@ -29,3 +31,5 @@ spec: app: vcluster-etcd release: {{ .Release.Name }} {{- end }} +{{- end }} +{{- end }} diff --git a/charts/k8s/templates/etcd-statefulset.yaml b/chart/templates/etcd-statefulset.yaml similarity index 57% rename from charts/k8s/templates/etcd-statefulset.yaml rename to chart/templates/etcd-statefulset.yaml index cd06fce5f..3fc7861d8 100644 --- a/charts/k8s/templates/etcd-statefulset.yaml +++ b/chart/templates/etcd-statefulset.yaml @@ -1,4 +1,7 @@ -{{ if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless ) (not .Values.etcd.disabled )) .Values.embeddedEtcd.migrateFromEtcd }} +{{- if not .Values.experimental.isolatedControlPlane.headless }} +{{- if or (include "vcluster.externalEtcd.enabled" .) (include "vcluster.externalEtcd.migrate" .) }} +{{- if .Values.controlPlane.backingStore.externalEtcd.statefulSet.enabled }} +{{- $externalEtcd := .Values.controlPlane.backingStore.externalEtcd.statefulSet }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -9,61 +12,61 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" -{{- if .Values.etcd.labels }} -{{ toYaml .Values.etcd.labels | indent 4 }} +{{- if $externalEtcd.labels }} +{{ toYaml $externalEtcd.labels | indent 4 }} {{- end }} - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.annotations }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations $externalEtcd.annotations }} {{- if $annotations }} annotations: {{ toYaml $annotations | indent 4 }} {{- end }} spec: + replicas: {{ $externalEtcd.highAvailability.replicas }} + podManagementPolicy: {{ $externalEtcd.scheduling.podManagementPolicy }} serviceName: {{ .Release.Name }}-etcd-headless - {{- if .Values.autoDeletePersistentVolumeClaims }} + {{- if eq $externalEtcd.persistence.volumeClaim.retentionPolicy "Delete" }} {{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete + whenDeleted: {{ $externalEtcd.persistence.volumeClaim.retentionPolicy }} {{- end }} {{- end }} - replicas: {{ .Values.etcd.replicas }} - podManagementPolicy: Parallel selector: matchLabels: app: vcluster-etcd release: {{ .Release.Name }} - {{- if (hasKey .Values.etcd "volumeClaimTemplates") }} + {{- if $externalEtcd.persistence.volumeClaimTemplates }} volumeClaimTemplates: -{{ toYaml .Values.etcd.volumeClaimTemplates | indent 4 }} - {{- else if .Values.etcd.storage.persistence }} +{{ toYaml $externalEtcd.persistence.volumeClaimTemplates | indent 4 }} + {{- else if not $externalEtcd.persistence.volumeClaim.disabled }} volumeClaimTemplates: - metadata: name: data spec: - accessModes: [ "ReadWriteOnce" ] - {{- if .Values.etcd.storage.className}} - storageClassName: {{ .Values.etcd.storage.className }} + accessModes: {{ $externalEtcd.persistence.volumeClaim.accessModes }} + {{- if $externalEtcd.persistence.volumeClaim.className }} + storageClassName: {{ $externalEtcd.persistence.volumeClaim.className }} {{- end }} resources: requests: - storage: {{ .Values.etcd.storage.size }} + storage: {{ $externalEtcd.persistence.volumeClaim.size }} {{- end }} template: metadata: - {{- if .Values.etcd.podAnnotations }} + {{- if $externalEtcd.pods.annotations }} annotations: -{{ toYaml .Values.etcd.podAnnotations | indent 8 }} - {{- end }} +{{ toYaml $externalEtcd.pods.annotations | indent 8 }} + {{- end }} labels: app: vcluster-etcd release: {{ .Release.Name }} - {{- range $k, $v := .Values.etcd.podLabels }} + {{- range $k, $v := $externalEtcd.pods.labels }} {{ $k }}: {{ $v | quote }} - {{- end }} + {{- end }} spec: terminationGracePeriodSeconds: 10 - {{- if .Values.etcd.affinity }} + {{- if $externalEtcd.scheduling.affinity }} affinity: -{{ toYaml .Values.etcd.affinity | indent 8 }} +{{ toYaml $externalEtcd.scheduling.affinity | indent 8 }} {{- else }} affinity: podAntiAffinity: @@ -97,17 +100,17 @@ spec: - {{ .Release.Name }} topologyKey: topology.kubernetes.io/zone {{- end }} - {{- if .Values.etcd.topologySpreadConstraints }} + {{- if $externalEtcd.scheduling.topologySpreadConstraints }} topologySpreadConstraints: -{{ toYaml .Values.etcd.topologySpreadConstraints | indent 8 }} +{{ toYaml $externalEtcd.scheduling.topologySpreadConstraints | indent 8 }} {{- end }} nodeSelector: -{{ toYaml .Values.etcd.nodeSelector | indent 8 }} +{{ toYaml $externalEtcd.scheduling.nodeSelector | indent 8 }} tolerations: -{{ toYaml .Values.etcd.tolerations | indent 8 }} +{{ toYaml $externalEtcd.scheduling.tolerations | indent 8 }} automountServiceAccountToken: false - {{- if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} + {{- if .Values.controlPlane.advanced.serviceAccount.name }} + serviceAccountName: {{ .Values.controlPlane.advanced.serviceAccount.name }} {{- else }} serviceAccountName: vc-{{ .Release.Name }} {{- end }} @@ -115,23 +118,23 @@ spec: - name: certs secret: secretName: {{ .Release.Name }}-certs - {{- if .Values.etcd.volumes }} -{{ toYaml .Values.etcd.volumes | indent 8 }} + {{- if $externalEtcd.persistence.addVolumes }} +{{ toYaml $externalEtcd.persistence.addVolumes | indent 8 }} {{- end }} - {{- if not .Values.etcd.storage.persistence }} + {{- if $externalEtcd.persistence.volumeClaim.disabled }} - name: data emptyDir: {} {{- end }} - {{- if .Values.etcd.priorityClassName }} - priorityClassName: {{ .Values.etcd.priorityClassName }} + {{- if $externalEtcd.scheduling.priorityClassName }} + priorityClassName: {{ $externalEtcd.scheduling.priorityClassName }} {{- end }} - {{- if .Values.etcd.fsGroup }} + {{- if $externalEtcd.security.podSecurityContext }} securityContext: - fsGroup: {{ .Values.etcd.fsGroup }} +{{ toYaml $externalEtcd.security.podSecurityContext | indent 8 }} {{- end }} containers: - name: etcd - image: "{{ .Values.defaultImageRegistry }}{{ .Values.etcd.image }}" + image: "{{ .Values.controlPlane.advanced.defaultImageRegistry }}{{ $externalEtcd.image.repository }}:{{ $externalEtcd.image.tag }}" command: - etcd - '--cert-file=/run/config/pki/etcd-server.crt' @@ -141,7 +144,7 @@ spec: - '--initial-advertise-peer-urls=https://$(NAME).{{ .Release.Name }}-etcd-headless.{{ .Release.Namespace }}:2380' {{- $releaseName := .Release.Name -}} {{- $releaseNamespace := .Release.Namespace }} - - '--initial-cluster={{ range $index := untilStep 0 (int .Values.etcd.replicas) 1 }}{{ if (ne (int $index) 0) }},{{ end }}{{ $releaseName }}-etcd-{{ $index }}=https://{{ $releaseName }}-etcd-{{ $index }}.{{ $releaseName }}-etcd-headless.{{ $releaseNamespace }}:2380{{ end }}' + - '--initial-cluster={{ range $index := untilStep 0 (int $externalEtcd.highAvailability.replicas) 1 }}{{ if (ne (int $index) 0) }},{{ end }}{{ $releaseName }}-etcd-{{ $index }}=https://{{ $releaseName }}-etcd-{{ $index }}.{{ $releaseName }}-etcd-headless.{{ $releaseNamespace }}:2380{{ end }}' - '--initial-cluster-token={{ .Release.Name }}' - '--initial-cluster-state=new' - '--listen-client-urls=https://0.0.0.0:2379' @@ -155,18 +158,20 @@ spec: - '--peer-trusted-ca-file=/run/config/pki/etcd-ca.crt' - '--snapshot-count=10000' - '--trusted-ca-file=/run/config/pki/etcd-ca.crt' - {{- range $f := .Values.etcd.extraArgs }} + {{- range $f := $externalEtcd.extraArgs }} - {{ $f | quote }} {{- end }} + {{- if $externalEtcd.security.containerSecurityContext }} securityContext: -{{ toYaml .Values.etcd.securityContext | indent 10 }} +{{ toYaml $externalEtcd.security.containerSecurityContext | indent 10 }} + {{- end }} env: - - name: NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if .Values.etcd.env }} -{{ toYaml .Values.etcd.env | indent 10 }} + - name: NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if $externalEtcd.env }} +{{ toYaml $externalEtcd.env | indent 10 }} {{- end }} volumeMounts: - name: data @@ -174,11 +179,11 @@ spec: - mountPath: /run/config/pki name: certs readOnly: true - {{- if .Values.etcd.volumeMounts }} -{{ toYaml .Values.etcd.volumeMounts | indent 10 }} + {{- if $externalEtcd.persistence.addVolumeMounts }} +{{ toYaml $externalEtcd.persistence.addVolumeMounts | indent 10 }} {{- end }} resources: -{{ toYaml .Values.etcd.resources | indent 10 }} +{{ toYaml $externalEtcd.resources | indent 10 }} livenessProbe: httpGet: path: /health @@ -199,7 +204,9 @@ spec: periodSeconds: 10 successThreshold: 1 failureThreshold: 24 - {{- if .Values.etcd.imagePullPolicy }} - imagePullPolicy: {{ .Values.etcd.imagePullPolicy }} + {{- if $externalEtcd.imagePullPolicy }} + imagePullPolicy: {{ $externalEtcd.imagePullPolicy }} {{- end }} -{{ end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/eks/templates/statefulset-service.yaml b/chart/templates/headless-service.yaml similarity index 58% rename from charts/eks/templates/statefulset-service.yaml rename to chart/templates/headless-service.yaml index e7b5d3d91..5131aee24 100644 --- a/charts/eks/templates/statefulset-service.yaml +++ b/chart/templates/headless-service.yaml @@ -1,15 +1,19 @@ -{{- if eq ( include "vcluster.kind" . ) "StatefulSet" }} +{{- if not .Values.experimental.isolatedControlPlane.headless }} +{{- if eq (include "vcluster.kind" .) "StatefulSet" }} apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }}-headless namespace: {{ .Release.Namespace }} labels: - app: {{ template "vcluster.fullname" . }} + app: vcluster chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} + {{- if .Values.controlPlane.advanced.headlessService.labels }} +{{ toYaml .Values.controlPlane.advanced.headlessService.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.headlessService.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} {{- if $annotations }} annotations: {{ toYaml $annotations | indent 4 }} @@ -21,7 +25,7 @@ spec: port: 443 targetPort: 8443 protocol: TCP - {{- if .Values.embeddedEtcd.enabled }} + {{- if .Values.controlPlane.backingStore.embeddedEtcd.enabled }} - name: etcd port: 2379 targetPort: 2379 @@ -36,3 +40,4 @@ spec: app: vcluster release: "{{ .Release.Name }}" {{- end }} +{{- end }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 000000000..4ff847bbd --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.controlPlane.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- $annotations := merge dict .Values.controlPlane.ingress.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} + {{- if $annotations }} + annotations: + {{- toYaml $annotations | nindent 4 }} + {{- end }} + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.ingress.labels }} +{{ toYaml .Values.controlPlane.ingress.labels | indent 4 }} + {{- end }} +spec: + {{- if .Values.controlPlane.ingress.spec }} +{{ toYaml .Values.controlPlane.ingress.spec | indent 2 }} + {{- end }} + {{- if not .Values.controlPlane.ingress.spec.rules }} + rules: + - host: {{ .Values.controlPlane.ingress.host | quote }} + http: + paths: + - backend: + service: + name: {{ .Release.Name }} + port: + name: https + path: / + pathType: {{ .Values.controlPlane.ingress.pathType }} + {{- end }} +{{- end }} diff --git a/chart/templates/limitrange.yaml b/chart/templates/limitrange.yaml new file mode 100644 index 000000000..f057b28e2 --- /dev/null +++ b/chart/templates/limitrange.yaml @@ -0,0 +1,35 @@ +{{- if .Values.policies.limitRange.enabled }} +apiVersion: v1 +kind: LimitRange +metadata: + name: vc-{{ .Release.Name }} + {{- if .Values.experimental.syncSettings.targetNamespace }} + namespace: {{ .Values.experimental.syncSettings.targetNamespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.policies.limitRange.labels }} +{{ toYaml .Values.policies.limitRange.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.policies.limitRange.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: + limits: + - default: + {{- range $key, $val := .Values.policies.limitRange.default }} + {{ $key }}: {{ $val | quote }} + {{- end }} + defaultRequest: + {{- range $key, $val := .Values.policies.limitRange.defaultRequest }} + {{ $key }}: {{ $val | quote }} + {{- end }} + type: Container +{{- end }} diff --git a/chart/templates/networkpolicy.yaml b/chart/templates/networkpolicy.yaml new file mode 100644 index 000000000..5be400598 --- /dev/null +++ b/chart/templates/networkpolicy.yaml @@ -0,0 +1,100 @@ +{{- if .Values.policies.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: vc-work-{{ .Release.Name }} + {{- if .Values.experimental.syncSettings.targetNamespace }} + namespace: {{ .Values.experimental.syncSettings.targetNamespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.policies.networkPolicy.labels }} +{{ toYaml .Values.policies.networkPolicy.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.policies.networkPolicy.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + vcluster.loft.sh/managed-by: {{ .Release.Name }} + egress: + # Allows outgoing connections to the vcluster control plane + - ports: + - port: 443 + - port: 8443 + to: + - podSelector: + matchLabels: + release: {{ .Release.Name }} + # Allows outgoing connections to DNS server + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # Allows outgoing connections to the internet or + # other vcluster workloads + - to: + - podSelector: + matchLabels: + vcluster.loft.sh/managed-by: {{ .Release.Name }} + - ipBlock: + cidr: {{ .Values.policies.networkPolicy.outgoingConnections.ipBlock.cidr }} + except: + {{- range .Values.policies.networkPolicy.outgoingConnections.ipBlock.except }} + - {{ . }} + {{- end }} + policyTypes: + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: vc-cp-{{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.policies.networkPolicy.labels }} +{{ toYaml .Values.policies.networkPolicy.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.policies.networkPolicy.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + release: {{ .Release.Name }} + egress: + # Allows outgoing connections to all pods with + # port 443, 8443 or 6443. This is needed for host Kubernetes + # access + - ports: + - port: 443 + - port: 8443 + - port: 6443 + # Allows outgoing connections to all vcluster workloads + # or kube system dns server + - to: + - podSelector: {} + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: 'kube-system' + podSelector: + matchLabels: + k8s-app: kube-dns + policyTypes: + - Egress + {{- end }} diff --git a/chart/templates/resourcequota.yaml b/chart/templates/resourcequota.yaml new file mode 100644 index 000000000..e5d1a689a --- /dev/null +++ b/chart/templates/resourcequota.yaml @@ -0,0 +1,43 @@ +{{- if .Values.policies.resourceQuota.enabled }} +apiVersion: v1 +kind: ResourceQuota +metadata: + name: vc-{{ .Release.Name }} + {{- if .Values.experimental.syncSettings.targetNamespace }} + namespace: {{ .Values.experimental.syncSettings.targetNamespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.policies.resourceQuota.labels }} +{{ toYaml .Values.policies.resourceQuota.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.policies.resourceQuota.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: + {{- if .Values.policies.resourceQuota.quota }} + hard: + {{- range $key, $val := .Values.policies.resourceQuota.quota }} + {{ $key }}: {{ $val | quote }} + {{- end }} + {{- end }} + + {{- if .Values.policies.resourceQuota.scopeSelector.matchExpressions }} + scopeSelector: + matchExpressions: +{{- toYaml .Values.policies.resourceQuota.scopeSelector.matchExpressions | nindent 6 }} + {{- end }} + + {{- if .Values.policies.resourceQuota.scopes }} + scopes: +{{- toYaml .Values.policies.resourceQuota.scopes | nindent 4 }} + {{- end}} + +{{- end }} diff --git a/charts/eks/templates/rbac/role.yaml b/chart/templates/role.yaml similarity index 59% rename from charts/eks/templates/rbac/role.yaml rename to chart/templates/role.yaml index de05bbb62..37ed162be 100644 --- a/charts/eks/templates/rbac/role.yaml +++ b/chart/templates/role.yaml @@ -1,104 +1,84 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} +{{- if .Values.rbac.role.enabled }} +{{- if .Values.experimental.multiNamespaceMode.enabled }} kind: ClusterRole {{- else -}} kind: Role {{- end }} apiVersion: rbac.authorization.k8s.io/v1 metadata: -{{- if .Values.multiNamespaceMode.enabled }} +{{- if .Values.experimental.multiNamespaceMode.enabled }} name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} {{- else }} - name: {{ .Release.Name }} -{{- end }} + name: vc-{{ .Release.Name }} namespace: {{ .Release.Namespace }} +{{- end }} labels: app: vcluster chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} {{- end }} rules: - {{- if .Values.pro }} - - apiGroups: [""] - {{- $resources := list "configmaps" "secrets" "services" "pods" "pods/attach" "pods/portforward" "pods/exec" "persistentvolumeclaims" }} - {{- range $excluded := .Values.rbac.role.excludedApiResources }} - {{- $resources = without $resources $excluded }} - {{- end}} - resources: {{ $resources | toJson }} - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] + {{- if .Values.rbac.role.overwriteRules }} +{{ toYaml .Values.rbac.role.overwriteRules | indent 2 }} {{- else }} - apiGroups: [""] resources: ["configmaps", "secrets", "services", "pods", "pods/attach", "pods/portforward", "pods/exec", "persistentvolumeclaims"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.status }} - apiGroups: [""] - resources: ["pods/status"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.ephemeralContainers }} - - apiGroups: [""] - resources: ["pods/ephemeralcontainers"] + resources: ["pods/status", "pods/ephemeralcontainers"] verbs: ["patch", "update"] - {{- end }} - {{- if or .Values.sync.endpoints.enabled .Values.headless }} + - apiGroups: ["apps"] + resources: ["statefulsets", "replicasets", "deployments"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["endpoints", "events", "pods/log"] + verbs: ["get", "list", "watch"] + {{- if or .Values.sync.toHost.endpoints.enabled .Values.experimental.isolatedControlPlane.headless }} - apiGroups: [""] resources: ["endpoints"] verbs: ["create", "delete", "patch", "update"] {{- end }} - {{- if gt (int .Values.syncer.replicas) 1 }} + {{- if gt (int .Values.controlPlane.statefulSet.highAvailability.replicas) 1 }} - apiGroups: ["coordination.k8s.io"] resources: ["leases"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - - apiGroups: [""] - resources: ["endpoints", "events", "pods/log"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.ingresses.enabled}} + {{- if .Values.observability.metrics.proxy.pods }} + - apiGroups: ["metrics.k8s.io"] + resources: ["pods"] + verbs: ["get", "list"] + {{- end }} + {{- if .Values.sync.toHost.ingresses.enabled}} - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - - apiGroups: ["apps"] - resources: ["statefulsets", "replicasets", "deployments"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.networkpolicies.enabled }} + {{- if .Values.sync.toHost.networkPolicies.enabled }} - apiGroups: ["networking.k8s.io"] resources: ["networkpolicies"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} + {{- if .Values.sync.toHost.volumeSnapshots.enabled }} - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshots"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.sync.serviceaccounts.enabled }} + {{- if .Values.sync.toHost.serviceAccounts.enabled }} - apiGroups: [""] resources: ["serviceaccounts"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.sync.poddisruptionbudgets.enabled }} + {{- if .Values.sync.toHost.podDisruptionBudgets.enabled }} - apiGroups: ["policy"] resources: ["poddisruptionbudgets"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] {{- end }} - {{- if .Values.openshift.enable }} - {{- if .Values.sync.endpoints.enabled }} - - apiGroups: [""] - resources: ["endpoints/restricted"] - verbs: ["create"] - {{- end }} - {{- end }} - {{- if .Values.proxy.metricsServer.pods.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["pods"] - verbs: ["get", "list"] - {{- end }} {{- include "vcluster.plugin.roleExtraRules" . | indent 2 }} {{- include "vcluster.generic.roleExtraRules" . | indent 2 }} {{- include "vcluster.rbac.roleExtraRules" . | indent 2 }} + {{- end }} {{- end }} diff --git a/charts/k3s/templates/rbac/rolebinding.yaml b/chart/templates/rolebinding.yaml similarity index 57% rename from charts/k3s/templates/rbac/rolebinding.yaml rename to chart/templates/rolebinding.yaml index 676e5002a..029510186 100644 --- a/charts/k3s/templates/rbac/rolebinding.yaml +++ b/chart/templates/rolebinding.yaml @@ -1,15 +1,15 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} +{{- if .Values.rbac.role.enabled }} +{{- if .Values.experimental.multiNamespaceMode.enabled }} kind: ClusterRoleBinding {{- else -}} kind: RoleBinding {{- end }} apiVersion: rbac.authorization.k8s.io/v1 metadata: -{{- if .Values.multiNamespaceMode.enabled }} +{{- if .Values.experimental.multiNamespaceMode.enabled }} name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} {{- else }} - name: {{ .Release.Name }} + name: vc-{{ .Release.Name }} namespace: {{ .Release.Namespace }} {{- end }} labels: @@ -17,25 +17,25 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} + {{- if .Values.controlPlane.advanced.globalMetadata.annotations }} annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} +{{ toYaml .Values.controlPlane.advanced.globalMetadata.annotations | indent 4 }} {{- end }} subjects: - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} + {{- if .Values.controlPlane.advanced.serviceAccount.name }} + name: {{ .Values.controlPlane.advanced.serviceAccount.name }} {{- else }} name: vc-{{ .Release.Name }} {{- end }} namespace: {{ .Release.Namespace }} roleRef: -{{- if .Values.multiNamespaceMode.enabled }} +{{- if .Values.experimental.multiNamespaceMode.enabled }} kind: ClusterRole name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} {{- else }} kind: Role - name: {{ .Release.Name }} + name: vc-{{ .Release.Name }} {{- end }} apiGroup: rbac.authorization.k8s.io {{- end }} diff --git a/charts/eks/templates/service-monitor.yaml b/chart/templates/service-monitor.yaml similarity index 53% rename from charts/eks/templates/service-monitor.yaml rename to chart/templates/service-monitor.yaml index 683506ab5..a92e166fe 100644 --- a/charts/eks/templates/service-monitor.yaml +++ b/chart/templates/service-monitor.yaml @@ -1,9 +1,22 @@ -{{- if .Values.monitoring.serviceMonitor.enabled }} +{{- if .Values.controlPlane.serviceMonitor.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - name: {{ .Release.Name }} + name: vc-{{ .Release.Name }} namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.serviceMonitor.labels }} +{{ toYaml .Values.controlPlane.serviceMonitor.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.serviceMonitor.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} spec: selector: matchLabels: diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 000000000..9142b87aa --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,46 @@ +{{- if .Values.controlPlane.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.service.labels }} +{{ toYaml .Values.controlPlane.service.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.globalMetadata.annotations .Values.controlPlane.service.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: +{{ toYaml .Values.controlPlane.service.spec | indent 2 }} + {{- if not .Values.controlPlane.service.spec.ports }} + ports: + - name: https + port: 443 + {{- if not .Values.experimental.isolatedControlPlane.headless }} + targetPort: 8443 + {{- end }} + nodePort: {{ .Values.controlPlane.service.httpsNodePort }} + protocol: TCP + {{- if or .Values.networking.advanced.proxyKubelets.byHostname .Values.networking.advanced.proxyKubelets.byIP }} + - name: kubelet + port: 10250 + {{- if not .Values.experimental.isolatedControlPlane.headless }} + targetPort: 8443 + {{- end }} + nodePort: {{ .Values.controlPlane.service.kubeletNodePort }} + protocol: TCP + {{- end }} + {{- end }} + {{- if and (not .Values.controlPlane.service.spec.selector) (not .Values.experimental.isolatedControlPlane.headless) }} + selector: + app: vcluster + release: {{ .Release.Name }} + {{- end }} +{{- end }} diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml new file mode 100644 index 000000000..6faa4d790 --- /dev/null +++ b/chart/templates/serviceaccount.yaml @@ -0,0 +1,28 @@ +{{- if .Values.controlPlane.advanced.serviceAccount.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if .Values.controlPlane.advanced.serviceAccount.name }} + name: {{ .Values.controlPlane.advanced.serviceAccount.name | quote }} + {{- else }} + name: vc-{{ .Release.Name }} + {{- end }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.advanced.serviceAccount.labels }} +{{ toYaml .Values.controlPlane.advanced.serviceAccount.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.serviceAccount.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} + {{- if $annotations }} + annotations: +{{- toYaml $annotations | nindent 4 }} + {{- end }} +{{- if .Values.controlPlane.advanced.serviceAccount.imagePullSecrets }} +imagePullSecrets: +{{ toYaml .Values.controlPlane.advanced.serviceAccount.imagePullSecrets | indent 2 }} +{{- end }} +{{- end }} diff --git a/chart/templates/statefulset.yaml b/chart/templates/statefulset.yaml new file mode 100644 index 000000000..712d889b0 --- /dev/null +++ b/chart/templates/statefulset.yaml @@ -0,0 +1,234 @@ +{{- if not .Values.experimental.isolatedControlPlane.headless }} +{{- if and .Values.controlPlane.backingStore.embeddedEtcd.enabled (include "vcluster.externalEtcd.enabled" .) -}} +{{- fail "embeddedEtcd and externalEtcd cannot be enabled at the same time together" }} +{{- end -}} +apiVersion: apps/v1 +kind: {{ include "vcluster.kind" . }} +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.statefulSet.labels }} +{{ toYaml .Values.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.statefulSet.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} + {{- if $annotations }} + annotations: +{{ toYaml $annotations | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: vcluster + release: {{ .Release.Name }} + {{- if eq (include "vcluster.kind" .) "StatefulSet" }} + {{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.controlPlane.statefulSet.persistence.volumeClaim.retentionPolicy }} + {{- end }} + serviceName: {{ .Release.Name }}-headless + podManagementPolicy: {{ .Values.controlPlane.statefulSet.scheduling.podManagementPolicy }} +{{ include "vcluster.persistence" . | indent 2 }} + {{- else }} + strategy: + rollingUpdate: + maxSurge: 1 + {{- if (eq (int .Values.controlPlane.statefulSet.highAvailability.replicas) 1) }} + maxUnavailable: 0 + {{- else }} + maxUnavailable: 1 + {{- end }} + type: RollingUpdate + {{- end }} + replicas: {{ .Values.controlPlane.statefulSet.highAvailability.replicas }} + template: + metadata: + annotations: + vClusterConfigHash: {{ .Values | toYaml | b64enc | sha256sum | quote }} + {{- if .Values.controlPlane.statefulSet.pods.annotations }} +{{ toYaml .Values.controlPlane.statefulSet.pods.annotations | indent 8 }} + {{- end }} + labels: + app: vcluster + release: {{ .Release.Name }} + {{- if .Values.controlPlane.statefulSet.pods.labels }} +{{ toYaml .Values.controlPlane.statefulSet.pods.labels | indent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: 10 + {{- if .Values.controlPlane.statefulSet.scheduling.priorityClassName }} + priorityClassName: {{ .Values.controlPlane.statefulSet.scheduling.priorityClassName }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.security.podSecurityContext }} + securityContext: +{{ toYaml .Values.controlPlane.statefulSet.security.podSecurityContext | indent 8 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.scheduling.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controlPlane.statefulSet.scheduling.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.scheduling.affinity }} + affinity: +{{ toYaml .Values.controlPlane.statefulSet.scheduling.affinity | indent 8 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.scheduling.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.controlPlane.statefulSet.scheduling.topologySpreadConstraints | indent 8 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.scheduling.tolerations }} + tolerations: +{{ toYaml .Values.controlPlane.statefulSet.scheduling.tolerations | indent 8 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.scheduling.priorityClassName }} + priorityClassName: {{ .Values.controlPlane.statefulSet.scheduling.priorityClassName }} + {{- end }} + {{- if .Values.controlPlane.advanced.serviceAccount.name }} + serviceAccountName: {{ .Values.controlPlane.advanced.serviceAccount.name }} + {{- else }} + serviceAccountName: vc-{{ .Release.Name }} + {{- end }} + volumes: +{{- include "vcluster.plugins.volumes" . | indent 8 }} + - name: helm-cache + emptyDir: {} + - name: binaries + emptyDir: {} + - name: tmp + emptyDir: {} + - name: certs + emptyDir: {} + {{- if eq (include "vcluster.distro" .) "k0s" }} + - name: run-k0s + emptyDir: {} + {{- end }} + {{- if eq (include "vcluster.distro" .) "k3s" }} + - name: k3s-config + emptyDir: {} + {{- end }} + - name: vcluster-config + secret: + secretName: vc-config-{{ .Release.Name }} + {{- if .Values.controlPlane.coredns.enabled }} + - name: coredns + configMap: + name: vc-coredns-{{ .Release.Name }} + # - name: custom-config-volume + # configMap: + # name: coredns-custom + # optional: true + {{- end }} + {{- if not (include "vcluster.persistence.volumeClaim.enabled" .) }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.controlPlane.statefulSet.persistence.addVolumes }} +{{ toYaml .Values.controlPlane.statefulSet.persistence.addVolumes | indent 8 }} + {{- end }} + {{- if (not .Values.experimental.syncSettings.disableSync) }} + initContainers: +{{ include "vcluster.initContainers" . | indent 8 }} + {{- end }} + containers: + - name: syncer + image: {{ include "vcluster.controlPlane.image" . | quote }} + imagePullPolicy: {{ .Values.controlPlane.statefulSet.imagePullPolicy }} + {{- if .Values.controlPlane.statefulSet.workingDir }} + workingDir: {{ .Values.controlPlane.statefulSet.workingDir }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.command }} + command: +{{ toYaml .Values.controlPlane.statefulSet.command | indent 12 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.args }} + args: +{{ toYaml .Values.controlPlane.statefulSet.args | indent 12 }} + {{- end }} + {{- if .Values.controlPlane.statefulSet.probes.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 8443 + scheme: HTTPS + failureThreshold: 60 + initialDelaySeconds: 60 + periodSeconds: 2 + {{- end }} + {{- if .Values.controlPlane.statefulSet.probes.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /readyz + port: 8443 + scheme: HTTPS + failureThreshold: 60 + periodSeconds: 2 + {{- end }} + {{- if .Values.controlPlane.statefulSet.probes.startupProbe.enabled }} + startupProbe: + httpGet: + path: /readyz + port: 8443 + scheme: HTTPS + failureThreshold: 300 + periodSeconds: 6 + {{- end }} + {{- if .Values.controlPlane.statefulSet.security.containerSecurityContext }} + securityContext: +{{ toYaml .Values.controlPlane.statefulSet.security.containerSecurityContext | indent 12 }} + {{- end }} + resources: +{{ toYaml .Values.controlPlane.statefulSet.resources | indent 12 }} + env: + - name: VCLUSTER_NAME + value: "{{ .Release.Name }}" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + {{- if .Values.controlPlane.statefulSet.env }} +{{ toYaml .Values.controlPlane.statefulSet.env | indent 12 }} + {{- end }} +{{ include "vcluster.distro.env" . | indent 12 }} + volumeMounts: +{{- include "vcluster.plugins.volumeMounts" . | indent 12 }} + - name: data + mountPath: /data + - name: binaries + mountPath: /binaries + - name: certs + mountPath: /pki + - name: helm-cache + mountPath: /.cache/helm + {{- if eq (include "vcluster.distro" .) "k0s" }} + - name: run-k0s + mountPath: /run/k0s + {{- end }} + {{- if eq (include "vcluster.distro" .) "k3s" }} + - name: k3s-config + mountPath: /etc/rancher + {{- end }} + - name: vcluster-config + mountPath: /var/vcluster + - name: tmp + mountPath: /tmp + {{- if .Values.controlPlane.coredns.enabled }} + - name: coredns + mountPath: /manifests/coredns + readOnly: true + {{- end }} + {{- if .Values.controlPlane.statefulSet.persistence.addVolumeMounts }} +{{ toYaml .Values.controlPlane.statefulSet.persistence.addVolumeMounts | indent 12 }} + {{- end }} +{{- include "vcluster.legacyPlugins.containers" . | indent 8 }} +{{- end }} diff --git a/chart/templates/workload-serviceaccount.yaml b/chart/templates/workload-serviceaccount.yaml new file mode 100644 index 000000000..91a24fd0a --- /dev/null +++ b/chart/templates/workload-serviceaccount.yaml @@ -0,0 +1,29 @@ +{{- if .Values.controlPlane.advanced.workloadServiceAccount.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if .Values.controlPlane.advanced.workloadServiceAccount.name }} + name: {{ .Values.controlPlane.advanced.workloadServiceAccount.name | quote }} + {{- else }} + name: vc-workload-{{ .Release.Name }} + {{- end }} + namespace: {{ .Release.Namespace }} + labels: + app: vcluster + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- if .Values.controlPlane.advanced.workloadServiceAccount.labels }} +{{ toYaml .Values.controlPlane.advanced.workloadServiceAccount.labels | indent 4 }} + {{- end }} + {{- $annotations := merge dict .Values.controlPlane.advanced.workloadServiceAccount.annotations .Values.controlPlane.advanced.globalMetadata.annotations }} + {{- if $annotations }} + annotations: +{{- toYaml $annotations | nindent 4 }} + {{- end }} +{{- $pullSecrets := concat .Values.controlPlane.advanced.serviceAccount.imagePullSecrets .Values.controlPlane.advanced.workloadServiceAccount.imagePullSecrets }} +{{- if $pullSecrets }} +imagePullSecrets: +{{ toYaml $pullSecrets | indent 2 }} +{{- end }} +{{- end }} diff --git a/charts/k3s/tests/README.md b/chart/tests/README.md similarity index 64% rename from charts/k3s/tests/README.md rename to chart/tests/README.md index bd826bdbe..56e90a96c 100644 --- a/charts/k3s/tests/README.md +++ b/chart/tests/README.md @@ -5,5 +5,10 @@ helm plugin install https://github.com/helm-unittest/helm-unittest.git Run tests via: ``` -helm unittest charts/k3s -d +helm unittest chart +``` + +To update the `values.schema.json` run: +``` +go run hack/schema/main.go ``` diff --git a/chart/tests/clusterrole_test.yaml b/chart/tests/clusterrole_test.yaml new file mode 100644 index 000000000..837eed9f8 --- /dev/null +++ b/chart/tests/clusterrole_test.yaml @@ -0,0 +1,213 @@ +suite: ClusterRoleBinding +templates: + - clusterrole.yaml + +tests: + - it: disable by default + asserts: + - hasDocuments: + count: 0 + + - it: enable by multi namespace mode + set: + experimental: + multiNamespaceMode: + enabled: true + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "namespaces", "serviceaccounts" ] + verbs: [ "create", "delete", "patch", "update", "get", "watch", "list" ] + + - it: override rules + set: + rbac: + clusterRole: + extraRules: + - apiGroups: [""] + resources: ["test123"] + verbs: ["test123"] + overwriteRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + + - it: extra rules + set: + sync: + toHost: + priorityClasses: + enabled: true + rbac: + clusterRole: + extraRules: + - apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 2 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + + - it: plugin rules + set: + plugin: + myTest: + rbac: + clusterRole: + extraRules: + - apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + plugins: + myTest2: + rbac: + clusterRole: + extraRules: + - apiGroups: [ "" ] + resources: [ "test1234" ] + verbs: [ "test1234" ] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 2 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "test1234" ] + verbs: [ "test1234" ] + + - it: replicate services + set: + networking: + replicateServices: + fromHost: + - from: test + to: other-test + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "services", "endpoints" ] + verbs: [ "get", "watch", "list" ] + + - it: real nodes + set: + sync: + fromHost: + nodes: + enabled: true + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "pods", "nodes", "nodes/status", "nodes/metrics", "nodes/stats", "nodes/proxy" ] + verbs: [ "get", "watch", "list" ] + + - it: virtual scheduler + set: + controlPlane: + advanced: + virtualScheduler: + enabled: true + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + content: + apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"] + verbs: ["get", "watch", "list"] + + - it: legacy pro + set: + pro: true + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 2 + - contains: + path: rules + content: + apiGroups: [ "" ] + resources: [ "pods", "nodes", "nodes/status", "nodes/metrics", "nodes/stats", "nodes/proxy" ] + verbs: [ "get", "watch", "list" ] + - contains: + path: rules + content: + apiGroups: [ "cluster.loft.sh", "storage.loft.sh" ] + resources: [ "features", "virtualclusters" ] + verbs: [ "get", "list", "watch" ] + + - it: metrics proxy + set: + observability: + metrics: + proxy: + nodes: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - contains: + path: rules + content: + apiGroups: [ "metrics.k8s.io" ] + resources: [ "nodes" ] + verbs: [ "get", "list" ] diff --git a/chart/tests/clusterrolebinding_test.yaml b/chart/tests/clusterrolebinding_test.yaml new file mode 100644 index 000000000..89a751b1b --- /dev/null +++ b/chart/tests/clusterrolebinding_test.yaml @@ -0,0 +1,141 @@ +suite: ClusterRoleBinding +templates: + - clusterrolebinding.yaml + +tests: + - it: disable by default + asserts: + - hasDocuments: + count: 0 + + - it: enable by multi namespace mode + set: + experimental: + multiNamespaceMode: + enabled: true + asserts: + - hasDocuments: + count: 1 + + - it: enable by from syncer + set: + sync: + fromHost: + ingressClasses: + enabled: true + asserts: + - hasDocuments: + count: 1 + + - it: enable by generic sync + set: + experimental: + genericSync: + clusterRole: + extraRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + asserts: + - hasDocuments: + count: 1 + + - it: enable by plugins + set: + plugins: + test: + rbac: + clusterRole: + extraRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + + - it: enable by plugin + set: + plugin: + test: + rbac: + clusterRole: + extraRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + + - it: enable by legacy api key + set: + pro: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ClusterRoleBinding + - equal: + path: metadata.name + value: vc-my-release-v-my-namespace + - notExists: + path: metadata.namespace + + - it: enable by extra rules + set: + rbac: + clusterRole: + extraRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ClusterRoleBinding + - equal: + path: metadata.name + value: vc-my-release-v-my-namespace + - notExists: + path: metadata.namespace + + - it: enable by overwrite rules + set: + rbac: + clusterRole: + overwriteRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ClusterRoleBinding + - equal: + path: metadata.name + value: vc-my-release-v-my-namespace + - notExists: + path: metadata.namespace + + diff --git a/chart/tests/coredns-configmap_test.yaml b/chart/tests/coredns-configmap_test.yaml new file mode 100644 index 000000000..4fe832b7a --- /dev/null +++ b/chart/tests/coredns-configmap_test.yaml @@ -0,0 +1,335 @@ +suite: CoreDNS Configmap +templates: + - coredns-configmap.yaml + +tests: + - it: should create configmap + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-coredns-my-release + - equal: + path: metadata.namespace + value: my-namespace + + - it: should create correct external coredns config + asserts: + - hasDocuments: + count: 1 + - notExists: + path: data.Corefile + - equal: + path: data["coredns.yaml"] + value: |- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: coredns + namespace: kube-system + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:coredns + rules: + - apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:coredns + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:coredns + subjects: + - kind: ServiceAccount + name: coredns + namespace: kube-system + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: coredns + namespace: kube-system + data: + Corefile: |- + .:1053 { + errors + health + ready + rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough cluster.local in-addr.arpa ip6.arpa + } + hosts /etc/NodeHosts { + ttl 60 + reload 15s + fallthrough + } + prometheus :9153 + forward . {{.HOST_CLUSTER_DNS}} + cache 30 + loop + loadbalance + } + + import /etc/coredns/custom/*.server + NodeHosts: "" + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: coredns + namespace: kube-system + labels: + k8s-app: kube-dns + kubernetes.io/name: "CoreDNS" + spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + selector: + matchLabels: + k8s-app: kube-dns + template: + metadata: + labels: + k8s-app: kube-dns + spec: + priorityClassName: "system-cluster-critical" + serviceAccountName: coredns + nodeSelector: + kubernetes.io/os: linux + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + k8s-app: kube-dns + containers: + - name: coredns + image: {{.IMAGE}} + imagePullPolicy: IfNotPresent + resources: + limits: + cpu: 1000m + memory: 170Mi + requests: + cpu: 20m + memory: 64Mi + args: [ "-conf", "/etc/coredns/Corefile" ] + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + readOnly: true + - name: custom-config-volume + mountPath: /etc/coredns/custom + readOnly: true + securityContext: + runAsNonRoot: true + runAsUser: {{.RUN_AS_USER}} + runAsGroup: {{.RUN_AS_GROUP}} + allowPrivilegeEscalation: false + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + readOnlyRootFilesystem: true + livenessProbe: + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ready + port: 8181 + scheme: HTTP + initialDelaySeconds: 0 + periodSeconds: 2 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + dnsPolicy: Default + volumes: + - name: config-volume + configMap: + name: coredns + items: + - key: Corefile + path: Corefile + - key: NodeHosts + path: NodeHosts + - name: custom-config-volume + configMap: + name: coredns-custom + optional: true + --- + apiVersion: v1 + kind: Service + metadata: + name: kube-dns + namespace: kube-system + annotations: + prometheus.io/port: "9153" + prometheus.io/scrape: "true" + labels: + k8s-app: kube-dns + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "CoreDNS" + spec: + type: ClusterIP + selector: + k8s-app: kube-dns + ports: + - name: dns + port: 53 + targetPort: 1053 + protocol: UDP + - name: dns-tcp + port: 53 + targetPort: 1053 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP + + - it: should create correct custom configmap + set: + controlPlane: + coredns: + embedded: true + overwriteManifests: |- + abc + asserts: + - hasDocuments: + count: 1 + - equal: + path: data["coredns.yaml"] + value: |- + abc + + - it: should create correct custom configmap + set: + controlPlane: + coredns: + embedded: true + overwriteConfig: |- + abc + asserts: + - hasDocuments: + count: 1 + - equal: + path: data.Corefile + value: |- + abc + + - it: should create correct embedded configmap + set: + controlPlane: + coredns: + embedded: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: data.Corefile + value: |- + .:1053 { + errors + health + ready + rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local + kubernetes cluster.local in-addr.arpa ip6.arpa { + kubeconfig /data/vcluster/admin.conf + pods insecure + fallthrough cluster.local in-addr.arpa ip6.arpa + } + hosts /etc/NodeHosts { + ttl 60 + reload 15s + fallthrough + } + prometheus :9153 + forward . {{.HOST_CLUSTER_DNS}} + cache 30 + loop + loadbalance + } + + import /etc/coredns/custom/*.server + - equal: + path: data["coredns.yaml"] + value: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: coredns + namespace: kube-system + data: + NodeHosts: "" + --- + apiVersion: v1 + kind: Service + metadata: + name: kube-dns + namespace: kube-system + annotations: + prometheus.io/port: "9153" + prometheus.io/scrape: "true" + labels: + k8s-app: kube-dns + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "CoreDNS" + spec: + type: ClusterIP + ports: + - name: dns + port: 53 + targetPort: 1053 + protocol: UDP + - name: dns-tcp + port: 53 + targetPort: 1053 + protocol: TCP + - name: metrics + port: 9153 + protocol: TCP diff --git a/chart/tests/etcd-headless-service_test.yaml b/chart/tests/etcd-headless-service_test.yaml new file mode 100644 index 000000000..acd9b7ddb --- /dev/null +++ b/chart/tests/etcd-headless-service_test.yaml @@ -0,0 +1,119 @@ +suite: External etcd headless Service +templates: + - etcd-headless-service.yaml + +tests: + - it: check disabled + asserts: + - hasDocuments: + count: 0 + + - it: enable for k3s & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + headlessService: + annotations: + test: test + distro: + k3s: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd-headless + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test + + - it: enable for k0s & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + headlessService: + annotations: + test: test + distro: + k0s: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd-headless + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test + + - it: enable for eks & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + headlessService: + annotations: + test: test + distro: + eks: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd-headless + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test + + - it: enable for k8s & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + headlessService: + annotations: + test: test + distro: + k8s: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd-headless + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test diff --git a/chart/tests/etcd-service_test.yaml b/chart/tests/etcd-service_test.yaml new file mode 100644 index 000000000..8fa039922 --- /dev/null +++ b/chart/tests/etcd-service_test.yaml @@ -0,0 +1,36 @@ +suite: External etcd Service +templates: + - etcd-service.yaml + +tests: + - it: check disabled + asserts: + - hasDocuments: + count: 0 + + - it: enable for k8s & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + service: + annotations: + test: test + distro: + k8s: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test diff --git a/chart/tests/etcd-statefulset_test.yaml b/chart/tests/etcd-statefulset_test.yaml new file mode 100644 index 000000000..7cf049a3d --- /dev/null +++ b/chart/tests/etcd-statefulset_test.yaml @@ -0,0 +1,121 @@ +suite: External etcd StatefulSet +templates: + - etcd-statefulset.yaml + +tests: + - it: check disabled + asserts: + - hasDocuments: + count: 0 + + - it: check disabled headless + set: + controlPlane: + distro: + k8s: + enabled: true + experimental: + isolatedControlPlane: + headless: true + asserts: + - hasDocuments: + count: 0 + + - it: enabled for k3s & non persistent + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + statefulSet: + extraArgs: + - "extra-arg" + env: + - name: my-new-env + persistence: + volumeClaim: + disabled: true + addVolumes: + - name: my-new-volume + addVolumeMounts: + - name: my-new-volume + asserts: + - hasDocuments: + count: 1 + - contains: + path: spec.template.spec.volumes + content: + name: "data" + emptyDir: {} + count: 1 + - notExists: + path: spec.volumeClaimTemplates + - contains: + path: spec.template.spec.volumes + content: + name: "my-new-volume" + count: 1 + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: "my-new-volume" + count: 1 + - contains: + path: spec.template.spec.containers[0].env + content: + name: "my-new-env" + count: 1 + - contains: + path: spec.template.spec.containers[0].command + content: "extra-arg" + count: 1 + + - it: enable for k8s & defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + backingStore: + externalEtcd: + statefulSet: + highAvailability: + replicas: 3 + annotations: + test: test + distro: + k8s: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release-etcd + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: metadata.annotations.test + value: test + - equal: + path: spec.replicas + value: 3 + - lengthEqual: + path: spec.volumeClaimTemplates + count: 1 + - lengthEqual: + path: spec.template.spec.volumes + count: 1 + - lengthEqual: + path: spec.template.spec.containers[0].volumeMounts + count: 2 + - lengthEqual: + path: spec.template.spec.containers[0].env + count: 1 + - notExists: + path: spec.template.spec.containers[0].args + - contains: + path: spec.template.spec.containers[0].command + content: "--initial-cluster=my-release-etcd-0=https://my-release-etcd-0.my-release-etcd-headless.my-namespace:2380,my-release-etcd-1=https://my-release-etcd-1.my-release-etcd-headless.my-namespace:2380,my-release-etcd-2=https://my-release-etcd-2.my-release-etcd-headless.my-namespace:2380" + count: 1 diff --git a/chart/tests/headless-service_test.yaml b/chart/tests/headless-service_test.yaml new file mode 100644 index 000000000..5b5c6832c --- /dev/null +++ b/chart/tests/headless-service_test.yaml @@ -0,0 +1,70 @@ +suite: ControlPlane StatefulSet +templates: + - headless-service.yaml + +tests: + - it: should not create control-plane + set: + experimental: + isolatedControlPlane: + headless: true + asserts: + - hasDocuments: + count: 0 + + - it: should not create if k8s + set: + controlPlane: + distro: + k8s: + enabled: true + asserts: + - hasDocuments: + count: 0 + + - it: should not create if stateless + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + asserts: + - hasDocuments: + count: 0 + + - it: name + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: spec.ports + count: 1 + - equal: + path: metadata.name + value: my-release-headless + - equal: + path: metadata.namespace + value: my-namespace + + - it: embedded-etcd + set: + controlPlane: + backingStore: + embeddedEtcd: + enabled: true + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: spec.ports + count: 3 + - equal: + path: spec.ports[1].name + value: etcd + - equal: + path: spec.ports[2].name + value: peer + diff --git a/chart/tests/ingress_test.yaml b/chart/tests/ingress_test.yaml new file mode 100644 index 000000000..c5a11d8c6 --- /dev/null +++ b/chart/tests/ingress_test.yaml @@ -0,0 +1,56 @@ +suite: ControlPlane Ingress +templates: + - ingress.yaml + +tests: + - it: should not create ingress by default + asserts: + - hasDocuments: + count: 0 + + - it: ingress defaults + set: + controlPlane: + ingress: + enabled: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release + - equal: + path: metadata.namespace + value: my-namespace + + - it: overwrite ingress tls + set: + controlPlane: + ingress: + enabled: true + host: my-host + spec: + tls: + - hosts: + - ingress-demo.example.com + secretName: ingress-demo-tls + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: spec.tls + count: 1 + - equal: + path: spec.rules[0].host + value: my-host + - contains: + path: spec.tls + count: 1 + content: + hosts: + - ingress-demo.example.com + secretName: ingress-demo-tls + diff --git a/chart/tests/limitrange_test.yaml b/chart/tests/limitrange_test.yaml new file mode 100644 index 000000000..aec52a717 --- /dev/null +++ b/chart/tests/limitrange_test.yaml @@ -0,0 +1,30 @@ +suite: LimitRange +templates: + - limitrange.yaml + +tests: + - it: should not create limit range by default + asserts: + - hasDocuments: + count: 0 + + - it: check defaults + release: + name: my-release + namespace: my-namespace + set: + policies: + limitRange: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + - lengthEqual: + path: spec.limits + count: 1 diff --git a/chart/tests/networkpolicy_test.yaml b/chart/tests/networkpolicy_test.yaml new file mode 100644 index 000000000..06da055f2 --- /dev/null +++ b/chart/tests/networkpolicy_test.yaml @@ -0,0 +1,49 @@ +suite: NetworkPolicy +templates: + - networkpolicy.yaml + +tests: + - it: should not create network policy by default + asserts: + - hasDocuments: + count: 0 + + - it: check defaults + release: + name: my-release + namespace: my-namespace + set: + policies: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 2 + - documentIndex: 0 + equal: + path: metadata.name + value: vc-work-my-release + - documentIndex: 0 + equal: + path: spec.egress[2].to[1].ipBlock.cidr + value: 0.0.0.0/0 + - documentIndex: 1 + equal: + path: metadata.name + value: vc-cp-my-release + - documentIndex: 0 + equal: + path: metadata.namespace + value: my-namespace + - documentIndex: 1 + equal: + path: metadata.namespace + value: my-namespace + - documentIndex: 0 + lengthEqual: + path: spec.egress + count: 3 + - documentIndex: 1 + lengthEqual: + path: spec.egress + count: 2 diff --git a/chart/tests/resourcequota_test.yaml b/chart/tests/resourcequota_test.yaml new file mode 100644 index 000000000..8f36f6d06 --- /dev/null +++ b/chart/tests/resourcequota_test.yaml @@ -0,0 +1,30 @@ +suite: ResourceQuota +templates: + - resourcequota.yaml + +tests: + - it: should not create resource quota by default + asserts: + - hasDocuments: + count: 0 + + - it: check defaults + release: + name: my-release + namespace: my-namespace + set: + policies: + resourceQuota: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: spec.hard["requests.cpu"] + value: "10" diff --git a/chart/tests/role_test.yaml b/chart/tests/role_test.yaml new file mode 100644 index 000000000..615075fd3 --- /dev/null +++ b/chart/tests/role_test.yaml @@ -0,0 +1,176 @@ +suite: Role +templates: + - role.yaml + +tests: + - it: check disabled + set: + rbac: + role: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: check overwrite rules + set: + rbac: + role: + overwriteRules: + - apiGroups: [ "" ] + resources: [ "configmaps" ] + verbs: [ "create" ] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 1 + - contains: + path: rules + count: 1 + content: + apiGroups: [ "" ] + resources: [ "configmaps" ] + verbs: [ "create" ] + + - it: check plugin extra rules + set: + plugin: + test123: + rbac: + role: + extraRules: + - apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + plugins: + test: + rbac: + role: + extraRules: + - apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 7 + - contains: + path: rules + count: 1 + content: + apiGroups: [ "" ] + resources: [ "test123" ] + verbs: [ "test123" ] + - contains: + path: rules + count: 1 + content: + apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + + - it: check generic sync + set: + experimental: + genericSync: + role: + extraRules: + - apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 6 + - contains: + path: rules + count: 1 + content: + apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + + - it: check extra rules + set: + rbac: + role: + extraRules: + - apiGroups: [""] + resources: ["test"] + verbs: ["test"] + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: rules + count: 6 + - contains: + path: rules + count: 1 + content: + apiGroups: [ "" ] + resources: [ "test" ] + verbs: [ "test" ] + + - it: check defaults + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Role + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + + - it: multi-namespace mode + set: + experimental: + multiNamespaceMode: + enabled: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ClusterRole + - equal: + path: metadata.name + value: vc-mn-my-release-v-my-namespace + + - it: metrics proxy + set: + observability: + metrics: + proxy: + pods: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Role + - contains: + path: rules + content: + apiGroups: [ "metrics.k8s.io" ] + resources: [ "pods" ] + verbs: [ "get", "list" ] diff --git a/chart/tests/rolebinding_test.yaml b/chart/tests/rolebinding_test.yaml new file mode 100644 index 000000000..6695445be --- /dev/null +++ b/chart/tests/rolebinding_test.yaml @@ -0,0 +1,59 @@ +suite: RoleBinding +templates: + - rolebinding.yaml + +tests: + - it: check defaults + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: RoleBinding + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: subjects[0].name + value: vc-my-release + - equal: + path: roleRef.kind + value: Role + - equal: + path: roleRef.name + value: vc-my-release + + - it: multi-namespace mode + set: + experimental: + multiNamespaceMode: + enabled: true + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ClusterRoleBinding + - equal: + path: metadata.name + value: vc-mn-my-release-v-my-namespace + - notExists: + path: metadata.namespace + - equal: + path: subjects[0].name + value: vc-my-release + - equal: + path: roleRef.kind + value: ClusterRole + - equal: + path: roleRef.name + value: vc-mn-my-release-v-my-namespace diff --git a/chart/tests/service-monitor_test.yaml b/chart/tests/service-monitor_test.yaml new file mode 100644 index 000000000..c05c0e04d --- /dev/null +++ b/chart/tests/service-monitor_test.yaml @@ -0,0 +1,33 @@ +suite: ServiceMonitor +templates: + - service-monitor.yaml + +tests: + - it: should not create service monitor by default + asserts: + - hasDocuments: + count: 0 + + - it: check defaults + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + serviceMonitor: + enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: spec.selector.matchLabels.app + value: vcluster + - lengthEqual: + path: spec.endpoints + count: 1 diff --git a/chart/tests/service_test.yaml b/chart/tests/service_test.yaml new file mode 100644 index 000000000..6f18fbe63 --- /dev/null +++ b/chart/tests/service_test.yaml @@ -0,0 +1,82 @@ +suite: ControlPlane Service +templates: + - service.yaml + +tests: + - it: should not create service + set: + controlPlane: + service: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should not create kubelet port + set: + networking: + advanced: + proxyKubelets: + byIP: false + byHostname: false + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: spec.ports + count: 1 + - contains: + path: spec.ports + content: + name: https + nodePort: 0 + targetPort: 8443 + protocol: TCP + port: 443 + + - it: service defaults + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: my-release + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: spec.type + value: ClusterIP + - equal: + path: spec.selector.app + value: vcluster + - lengthEqual: + path: spec.ports + count: 2 + + - it: isolated control plane + release: + name: my-release + namespace: my-namespace + set: + experimental: + isolatedControlPlane: + headless: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.type + value: ClusterIP + - lengthEqual: + path: spec.ports + count: 2 + - notExists: + path: spec.ports[0].targetPort + - notExists: + path: spec.ports[1].targetPort + - notExists: + path: spec.selector diff --git a/chart/tests/serviceaccount_test.yaml b/chart/tests/serviceaccount_test.yaml new file mode 100644 index 000000000..4441181e8 --- /dev/null +++ b/chart/tests/serviceaccount_test.yaml @@ -0,0 +1,61 @@ +suite: ControlPlane ServiceAccount +templates: + - serviceaccount.yaml + +tests: + - it: should not create service account + set: + controlPlane: + advanced: + serviceAccount: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should create service account + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-my-release + - equal: + path: metadata.namespace + value: my-namespace + + - it: should create service account with name + set: + controlPlane: + advanced: + serviceAccount: + name: test + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: test + + - it: should create image pull secrets + set: + controlPlane: + advanced: + serviceAccount: + imagePullSecrets: + - name: test1 + workloadServiceAccount: + imagePullSecrets: + - name: test2 + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: imagePullSecrets + count: 1 + - equal: + path: imagePullSecrets[0].name + value: test1 diff --git a/chart/tests/statefulset_test.yaml b/chart/tests/statefulset_test.yaml new file mode 100644 index 000000000..ecf8bdb79 --- /dev/null +++ b/chart/tests/statefulset_test.yaml @@ -0,0 +1,300 @@ +suite: ControlPlane StatefulSet +templates: + - statefulset.yaml + +tests: + - it: should not create control-plane + set: + experimental: + isolatedControlPlane: + headless: true + asserts: + - hasDocuments: + count: 0 + + - it: name & defaults + release: + name: my-release + namespace: my-namespace + capabilities: + majorVersion: 1 + minorVersion: 29 + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: StatefulSet + - lengthEqual: + path: spec.template.spec.containers + count: 1 + - contains: + path: spec.template.spec.containers[0].env + content: + name: VCLUSTER_NAME + value: my-release + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: data + mountPath: /data + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: binaries + mountPath: /binaries + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: certs + mountPath: /pki + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: helm-cache + mountPath: /.cache/helm + - equal: + path: metadata.name + value: my-release + - equal: + path: metadata.namespace + value: my-namespace + - equal: + path: spec.podManagementPolicy + value: Parallel + - equal: + path: spec.persistentVolumeClaimRetentionPolicy.whenDeleted + value: Retain + - equal: + path: spec.replicas + value: 1 + - equal: + path: spec.template.metadata.labels.app + value: vcluster + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 10 + - equal: + path: spec.volumeClaimTemplates[0].spec.accessModes[0] + value: ReadWriteOnce + - equal: + path: spec.volumeClaimTemplates[0].spec.resources.requests.storage + value: 5Gi + + - it: fail when both backing stores are enabled + set: + controlPlane: + backingStore: + embeddedEtcd: + enabled: true + externalEtcd: + enabled: true + asserts: + - failedTemplate: + errorMessage: "embeddedEtcd and externalEtcd cannot be enabled at the same time together" + + - it: not persistent when external etcd is enabled + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + asserts: + - equal: + path: kind + value: Deployment + - notExists: + path: spec.volumeClaimTemplates + + - it: not persistent when k8s + set: + controlPlane: + distro: + k8s: + enabled: true + asserts: + - equal: + path: kind + value: Deployment + - notExists: + path: spec.volumeClaimTemplates + + - it: persistent when k8s and embedded etcd + set: + controlPlane: + backingStore: + embeddedEtcd: + enabled: true + distro: + k8s: + enabled: true + asserts: + - equal: + path: kind + value: StatefulSet + - lengthEqual: + path: spec.volumeClaimTemplates + count: 1 + + - it: plugin 1 + set: + plugins: + test: + image: test + plugin: + test123: + version: v2 + image: test + asserts: + - lengthEqual: + path: spec.template.spec.volumes + count: 8 + - lengthEqual: + path: spec.template.spec.initContainers + count: 3 + + - it: plugin volumes 2 + set: + controlPlane: + distro: + k0s: + enabled: true + plugin: + test: + version: v2 + image: test + asserts: + - equal: + path: kind + value: StatefulSet + - lengthEqual: + path: spec.template.spec.volumes + count: 8 + - lengthEqual: + path: spec.template.spec.initContainers + count: 2 + + - it: plugin volumes 3 + set: + plugin: + test: + image: test + asserts: + - lengthEqual: + path: spec.template.spec.volumes + count: 7 + - lengthEqual: + path: spec.template.spec.initContainers + count: 1 + + - it: add volumes + set: + controlPlane: + statefulSet: + persistence: + addVolumes: + - name: myVolume + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: myVolume + - lengthEqual: + path: spec.template.spec.volumes + count: 8 + + - it: enable k8s + set: + controlPlane: + distro: + k8s: + enabled: true + asserts: + - equal: + path: kind + value: Deployment + - notExists: + path: spec.volumeClaimTemplates + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + + - it: enable k8s + release: + name: my-release + namespace: my-namespace + set: + controlPlane: + distro: + k8s: + enabled: true + statefulSet: + persistence: + volumeClaim: + disabled: true + volumeClaimTemplates: + - metadata: + name: data + spec: + resources: + requests: + storage: 5Gi + asserts: + - equal: + path: kind + value: StatefulSet + - equal: + path: spec.serviceName + value: my-release-headless + - contains: + path: spec.volumeClaimTemplates + content: + metadata: + name: data + spec: + resources: + requests: + storage: 5Gi + + - it: enable eks + set: + controlPlane: + distro: + eks: + enabled: true + asserts: + - equal: + path: kind + value: Deployment + - notExists: + path: spec.volumeClaimTemplates + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + + - it: enable k0s + set: + controlPlane: + backingStore: + externalEtcd: + enabled: true + distro: + k0s: + enabled: true + asserts: + - equal: + path: kind + value: Deployment + - notExists: + path: spec.volumeClaimTemplates + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + + diff --git a/chart/tests/workload-serviceaccount_test.yaml b/chart/tests/workload-serviceaccount_test.yaml new file mode 100644 index 000000000..8b2ced477 --- /dev/null +++ b/chart/tests/workload-serviceaccount_test.yaml @@ -0,0 +1,64 @@ +suite: Workload ServiceAccount +templates: + - workload-serviceaccount.yaml + +tests: + - it: should not create service account + set: + controlPlane: + advanced: + workloadServiceAccount: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: should create service account + release: + name: my-release + namespace: my-namespace + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: vc-workload-my-release + - equal: + path: metadata.namespace + value: my-namespace + + - it: should create service account with name + set: + controlPlane: + advanced: + workloadServiceAccount: + name: test + asserts: + - hasDocuments: + count: 1 + - equal: + path: metadata.name + value: test + + - it: should create image pull secrets + set: + controlPlane: + advanced: + serviceAccount: + imagePullSecrets: + - name: test1 + workloadServiceAccount: + imagePullSecrets: + - name: test2 + asserts: + - hasDocuments: + count: 1 + - lengthEqual: + path: imagePullSecrets + count: 2 + - equal: + path: imagePullSecrets[0].name + value: test1 + - equal: + path: imagePullSecrets[1].name + value: test2 diff --git a/chart/values.schema.json b/chart/values.schema.json new file mode 100755 index 000000000..ccee3d0b7 --- /dev/null +++ b/chart/values.schema.json @@ -0,0 +1,2657 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://vcluster.com/schemas/config", + "$defs": { + "BackingStore": { + "properties": { + "embeddedEtcd": { + "$ref": "#/$defs/EmbeddedEtcd", + "description": "EmbeddedEtcd defines to use embedded etcd as a storage backend for the vCluster" + }, + "externalEtcd": { + "$ref": "#/$defs/ExternalEtcd", + "description": "ExternalEtcd defines to use an external etcd deployed by the helm chart as a storage backend for the vCluster" + } + }, + "additionalProperties": false, + "type": "object" + }, + "CentralAdmission": { + "properties": { + "validatingWebhooks": { + "items": true, + "type": "array", + "description": "ValidatingWebhooks are validating webhooks that should be enforced in the vCluster" + }, + "mutatingWebhooks": { + "items": true, + "type": "array", + "description": "MutatingWebhooks are mutating webhooks that should be enforced in the vCluster" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlane": { + "properties": { + "distro": { + "$ref": "#/$defs/Distro", + "description": "Distro holds vCluster related distro options." + }, + "backingStore": { + "$ref": "#/$defs/BackingStore", + "description": "BackingStore defines which backing store to use for vCluster. If not defined will fallback to the default distro backing store." + }, + "coredns": { + "$ref": "#/$defs/CoreDNS", + "description": "CoreDNS defines everything coredns related." + }, + "proxy": { + "$ref": "#/$defs/ControlPlaneProxy", + "description": "Proxy defines options for the vCluster control plane proxy that is used to do authentication and intercept requests." + }, + "service": { + "$ref": "#/$defs/ControlPlaneService", + "description": "Service defines options for the vCluster service deployed by helm." + }, + "ingress": { + "$ref": "#/$defs/ControlPlaneIngress", + "description": "Ingress defines options for the vCluster ingress deployed by helm." + }, + "statefulSet": { + "$ref": "#/$defs/ControlPlaneStatefulSet", + "description": "StatefulSet defines options for the vCluster statefulSet deployed by helm." + }, + "hostPathMapper": { + "$ref": "#/$defs/HostPathMapper", + "description": "HostPathMapper defines if vCluster should rewrite host paths." + }, + "serviceMonitor": { + "$ref": "#/$defs/ServiceMonitor", + "description": "ServiceMonitor can be used to automatically create a service monitor for vCluster deployment itself." + }, + "advanced": { + "$ref": "#/$defs/ControlPlaneAdvanced", + "description": "Advanced holds additional configuration for the vCluster control plane." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneAdvanced": { + "properties": { + "defaultImageRegistry": { + "type": "string", + "description": "DefaultImageRegistry will be used as a prefix for all internal images deployed by vCluster or helm. This makes it easy to\nupload all required vCluster images to a single private repository and set this value. Workload images are not affected by this." + }, + "virtualScheduler": { + "$ref": "#/$defs/EnableSwitch", + "description": "VirtualScheduler defines if a scheduler should be used within the vCluster or the scheduling decision for workloads will be made by the host cluster." + }, + "serviceAccount": { + "$ref": "#/$defs/ControlPlaneServiceAccount", + "description": "ServiceAccount specifies options for the vCluster control-plane service account." + }, + "workloadServiceAccount": { + "$ref": "#/$defs/ControlPlaneWorkloadServiceAccount", + "description": "WorkloadServiceAccount specifies options for the service account that will be used for the workloads that run within the vCluster." + }, + "headlessService": { + "$ref": "#/$defs/ControlPlaneHeadlessService", + "description": "HeadlessService specifies options for the headless service used for the vCluster statefulSet." + }, + "globalMetadata": { + "$ref": "#/$defs/ControlPlaneGlobalMetadata", + "description": "GlobalMetadata is metadata that will be added to all resources deployed by helm." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneGlobalMetadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneHeadlessService": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneHighAvailability": { + "properties": { + "replicas": { + "type": "integer", + "description": "Replicas is the amount of replicas to use for the statefulSet." + }, + "leaseDuration": { + "type": "integer", + "description": "LeaseDuration is the time to lease for the leader." + }, + "renewDeadline": { + "type": "integer", + "description": "RenewDeadline is the deadline to renew a lease for the leader." + }, + "retryPeriod": { + "type": "integer", + "description": "RetryPeriod is the time until a replica will retry to get a lease." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneIngress": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the control plane ingress should be enabled" + }, + "host": { + "type": "string", + "description": "Host is the host where vCluster will be reachable" + }, + "pathType": { + "type": "string", + "description": "PathType is the path type of the ingress" + }, + "spec": { + "type": "object", + "description": "Spec allows you to configure extra ingress options." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlanePersistence": { + "properties": { + "volumeClaim": { + "$ref": "#/$defs/VolumeClaim", + "description": "VolumeClaim can be used to configure the persistent volume claim." + }, + "volumeClaimTemplates": { + "items": { + "type": "object" + }, + "type": "array", + "description": "VolumeClaimTemplates defines the volumeClaimTemplates for the statefulSet" + }, + "addVolumes": { + "items": { + "type": "object" + }, + "type": "array", + "description": "AddVolumes defines extra volumes for the pod" + }, + "addVolumeMounts": { + "items": { + "$ref": "#/$defs/VolumeMount" + }, + "type": "array", + "description": "AddVolumeMounts defines extra volume mounts for the container" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneProbes": { + "properties": { + "livenessProbe": { + "$ref": "#/$defs/EnableSwitch", + "description": "LivenessProbe specifies if the liveness probe for the container should be enabled" + }, + "readinessProbe": { + "$ref": "#/$defs/EnableSwitch", + "description": "ReadinessProbe specifies if the readiness probe for the container should be enabled" + }, + "startupProbe": { + "$ref": "#/$defs/EnableSwitch", + "description": "StartupProbe specifies if the startup probe for the container should be enabled" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneProxy": { + "properties": { + "bindAddress": { + "type": "string", + "description": "BindAddress under which the vCluster will expose the proxy." + }, + "port": { + "type": "integer", + "description": "Port under which the vCluster will expose the proxy." + }, + "extraSANs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraSANs are extra hostnames to sign the vCluster proxy certificate for." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneScheduling": { + "properties": { + "nodeSelector": { + "type": "object", + "description": "NodeSelector is the node selector to apply to the pod." + }, + "affinity": { + "type": "object", + "description": "Affinity is the affinity to apply to the pod." + }, + "tolerations": { + "items": true, + "type": "array", + "description": "Tolerations are the tolerations to apply to the pod." + }, + "priorityClassName": { + "type": "string", + "description": "PriorityClassName is the priority class name for the the pod." + }, + "podManagementPolicy": { + "type": "string", + "description": "PodManagementPolicy is the statefulSet pod management policy." + }, + "topologySpreadConstraints": { + "items": true, + "type": "array", + "description": "TopologySpreadConstraints are the topology spread constraints for the pod." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneSecurity": { + "properties": { + "podSecurityContext": { + "type": "object", + "description": "PodSecurityContext specifies security context options on the pod level." + }, + "containerSecurityContext": { + "type": "object", + "description": "ContainerSecurityContext specifies security context options on the container level." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneService": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the control plane service should be enabled" + }, + "kubeletNodePort": { + "type": "integer", + "description": "KubeletNodePort is the node port where the fake kubelet is exposed. Defaults to 0." + }, + "httpsNodePort": { + "type": "integer", + "description": "HTTPSNodePort is the node port where https is exposed. Defaults to 0." + }, + "spec": { + "type": "object", + "description": "Spec allows you to configure extra service options." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneServiceAccount": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the service account should get deployed." + }, + "name": { + "type": "string", + "description": "Name specifies what name to use for the service account." + }, + "imagePullSecrets": { + "items": { + "$ref": "#/$defs/LocalObjectReference" + }, + "type": "array", + "description": "ImagePullSecrets defines extra image pull secrets for the service account." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneStatefulSet": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + }, + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the image for the controlPlane statefulSet container" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the policy how to pull the image." + }, + "workingDir": { + "type": "string", + "description": "WorkingDir specifies in what folder the main process should get started." + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command allows you to override the main command." + }, + "args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Args allows you to override the main arguments." + }, + "env": { + "items": { + "type": "object" + }, + "type": "array", + "description": "Env are additional environment variables for the statefulSet container." + }, + "pods": { + "$ref": "#/$defs/LabelsAndAnnotations", + "description": "Pods are additional labels or annotations for the statefulSet pod." + }, + "probes": { + "$ref": "#/$defs/ControlPlaneProbes", + "description": "Probes enables or disables the main container probes." + }, + "security": { + "$ref": "#/$defs/ControlPlaneSecurity", + "description": "Security defines pod or container security context." + }, + "persistence": { + "$ref": "#/$defs/ControlPlanePersistence", + "description": "Persistence defines options around persistence for the statefulSet." + }, + "scheduling": { + "$ref": "#/$defs/ControlPlaneScheduling", + "description": "Scheduling holds options related to scheduling." + }, + "highAvailability": { + "$ref": "#/$defs/ControlPlaneHighAvailability", + "description": "HighAvailability holds options related to high availability." + }, + "resources": { + "$ref": "#/$defs/Resources", + "description": "Resources are the resource requests and limits for the statefulSet container." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ControlPlaneWorkloadServiceAccount": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the service account for the workloads should get deployed." + }, + "name": { + "type": "string", + "description": "Name specifies what name to use for the service account for the vCluster workloads." + }, + "imagePullSecrets": { + "items": { + "$ref": "#/$defs/LocalObjectReference" + }, + "type": "array", + "description": "ImagePullSecrets defines extra image pull secrets for the workload service account." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "CoreDNS": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if coredns is enabled" + }, + "embedded": { + "type": "boolean", + "description": "Embedded defines if vCluster will start the embedded coredns service" + }, + "service": { + "$ref": "#/$defs/CoreDNSService", + "description": "Service holds extra options for the coredns service deployed within the vCluster" + }, + "deployment": { + "$ref": "#/$defs/CoreDNSDeployment", + "description": "Deployment holds extra options for the coredns deployment deployed within the vCluster" + }, + "overwriteConfig": { + "type": "string", + "description": "OverwriteConfig can be used to overwrite the coredns config" + }, + "overwriteManifests": { + "type": "string", + "description": "OverwriteManifests can be used to overwrite the coredns manifests used to deploy coredns" + } + }, + "additionalProperties": false, + "type": "object" + }, + "CoreDNSDeployment": { + "properties": { + "image": { + "type": "string", + "description": "Image is the coredns image to use" + }, + "replicas": { + "type": "integer", + "description": "Replicas is the amount of coredns pods to run." + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "NodeSelector is the node selector to use for coredns." + }, + "resources": { + "$ref": "#/$defs/Resources", + "description": "Resources are the desired resources for coredns." + }, + "pods": { + "$ref": "#/$defs/LabelsAndAnnotations", + "description": "Pods is additional metadata for the coredns pods." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "CoreDNSService": { + "properties": { + "spec": { + "type": "object", + "description": "Spec holds extra options for the coredns service" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Distro": { + "properties": { + "k3s": { + "$ref": "#/$defs/DistroK3s", + "description": "K3S holds k3s relevant configuration." + }, + "k0s": { + "$ref": "#/$defs/DistroK0s", + "description": "K0S holds k0s relevant configuration." + }, + "k8s": { + "$ref": "#/$defs/DistroK8s", + "description": "K8S holds k8s relevant configuration." + }, + "eks": { + "$ref": "#/$defs/DistroK8s", + "description": "EKS holds eks relevant configuration." + } + }, + "additionalProperties": false, + "type": "object" + }, + "DistroContainer": { + "properties": { + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the distro image" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy for the distro image" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command to start the distro binary. This will override the existing command." + }, + "extraArgs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraArgs are additional arguments to pass to the distro binary." + } + }, + "additionalProperties": false, + "type": "object" + }, + "DistroContainerDisabled": { + "properties": { + "disabled": { + "type": "boolean", + "description": "Disabled signals this container should be disabled." + }, + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the distro image" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy for the distro image" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command to start the distro binary. This will override the existing command." + }, + "extraArgs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraArgs are additional arguments to pass to the distro binary." + } + }, + "additionalProperties": false, + "type": "object" + }, + "DistroK0s": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the k0s distro should be enabled. Only one distro can be enabled at the same time." + }, + "config": { + "type": "string", + "description": "Config allows you to override the k0s config passed to the k0s binary." + }, + "env": { + "items": { + "type": "object" + }, + "type": "array", + "description": "Env are extra environment variables to use for the main container." + }, + "securityContext": { + "type": "object", + "description": "SecurityContext can be used for the distro init container" + }, + "resources": { + "type": "object", + "description": "Resources are the resources for the distro init container" + }, + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the distro image" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy for the distro image" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command to start the distro binary. This will override the existing command." + }, + "extraArgs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraArgs are additional arguments to pass to the distro binary." + } + }, + "additionalProperties": false, + "type": "object" + }, + "DistroK3s": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the k3s distro should be enabled. Only one distro can be enabled at the same time." + }, + "token": { + "type": "string", + "description": "Token is the k3s token to use. If empty, vCluster will choose one." + }, + "env": { + "items": { + "type": "object" + }, + "type": "array", + "description": "Env are extra environment variables to use for the main container." + }, + "securityContext": { + "type": "object", + "description": "SecurityContext can be used for the distro init container" + }, + "resources": { + "type": "object", + "description": "Resources are the resources for the distro init container" + }, + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the distro image" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy for the distro image" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command to start the distro binary. This will override the existing command." + }, + "extraArgs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraArgs are additional arguments to pass to the distro binary." + } + }, + "additionalProperties": false, + "type": "object" + }, + "DistroK8s": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the k8s distro should be enabled. Only one distro can be enabled at the same time." + }, + "apiServer": { + "$ref": "#/$defs/DistroContainerDisabled", + "description": "APIServer holds configuration specific to starting the api server." + }, + "controllerManager": { + "$ref": "#/$defs/DistroContainerDisabled", + "description": "ControllerManager holds configuration specific to starting the scheduler." + }, + "scheduler": { + "$ref": "#/$defs/DistroContainer", + "description": "Scheduler holds configuration specific to starting the scheduler." + }, + "env": { + "items": { + "type": "object" + }, + "type": "array", + "description": "Env are extra environment variables to use for the main container." + }, + "securityContext": { + "type": "object", + "description": "SecurityContext can be used for the distro init container" + }, + "resources": { + "type": "object", + "description": "Resources are the resources for the distro init container" + } + }, + "additionalProperties": false, + "type": "object" + }, + "EmbeddedEtcd": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the embedded etcd should be used." + }, + "migrateFromExternalEtcd": { + "type": "boolean", + "description": "MigrateFromExternalEtcd signals that vCluster should migrate from the external etcd." + } + }, + "additionalProperties": false, + "type": "object" + }, + "EnableSwitch": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if this option should be enabled." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Experimental": { + "properties": { + "isolatedControlPlane": { + "$ref": "#/$defs/ExperimentalIsolatedControlPlane", + "description": "IsolatedControlPlane is a feature to run the vCluster control plane in a different Kubernetes cluster than the workloads themselves." + }, + "syncSettings": { + "$ref": "#/$defs/ExperimentalSyncSettings", + "description": "SyncSettings are advanced settings for the syncer controller." + }, + "genericSync": { + "$ref": "#/$defs/ExperimentalGenericSync", + "description": "GenericSync holds options to generically sync resources from vCluster to host." + }, + "deploy": { + "$ref": "#/$defs/ExperimentalDeploy", + "description": "Deploy allows you to configure manifests and helm charts to deploy within the vCluster." + }, + "multiNamespaceMode": { + "$ref": "#/$defs/ExperimentalMultiNamespaceMode", + "description": "MultiNamespaceMode tells vCluster to sync to multiple namespaces instead of a single one. This will map each vCluster namespace to a single namespace in the host cluster." + }, + "virtualClusterKubeConfig": { + "$ref": "#/$defs/VirtualClusterKubeConfig", + "description": "VirtualClusterKubeConfig allows you to override distro specifics and specify where vCluster will find the required certificates and vCluster config." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalDeploy": { + "properties": { + "manifests": { + "type": "string", + "description": "Manifests are raw kubernetes manifests that should get applied within the vCluster." + }, + "manifestsTemplate": { + "type": "string", + "description": "ManifestsTemplate is a kubernetes manifest template that will be rendered with vCluster values before applying it within the vCluster." + }, + "helm": { + "items": { + "$ref": "#/$defs/ExperimentalDeployHelm" + }, + "type": "array", + "description": "Helm are helm charts that should get deployed into the vCluster" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalDeployHelm": { + "properties": { + "chart": { + "$ref": "#/$defs/ExperimentalDeployHelmChart", + "description": "Chart defines what chart should get deployed." + }, + "release": { + "$ref": "#/$defs/ExperimentalDeployHelmRelease", + "description": "Release defines what release should get deployed." + }, + "values": { + "type": "string", + "description": "Values defines what values should get used." + }, + "timeout": { + "type": "string", + "description": "Timeout defines the timeout for helm" + }, + "bundle": { + "type": "string", + "description": "Bundle allows to compress the helm chart and specify this instead of an online chart" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalDeployHelmChart": { + "properties": { + "name": { + "type": "string" + }, + "repo": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "version": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalDeployHelmRelease": { + "properties": { + "name": { + "type": "string", + "description": "Name of the release" + }, + "namespace": { + "type": "string", + "description": "Namespace of the release" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalGenericSync": { + "properties": { + "version": { + "type": "string", + "description": "Version is the config version" + }, + "export": { + "items": { + "$ref": "#/$defs/Export" + }, + "type": "array", + "description": "Exports syncs a resource from the virtual cluster to the host" + }, + "import": { + "items": { + "$ref": "#/$defs/Import" + }, + "type": "array", + "description": "Imports syncs a resource from the host cluster to virtual cluster" + }, + "hooks": { + "$ref": "#/$defs/Hooks", + "description": "Hooks are hooks that can be used to inject custom patches before syncing" + }, + "clusterRole": { + "$ref": "#/$defs/ExperimentalGenericSyncExtraRules" + }, + "role": { + "$ref": "#/$defs/ExperimentalGenericSyncExtraRules" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalGenericSyncExtraRules": { + "properties": { + "extraRules": { + "items": true, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalIsolatedControlPlane": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the isolated control plane feature should be enabled." + }, + "headless": { + "type": "boolean", + "description": "Headless states that helm should deploy the vCluster in headless mode for the isolated control plane." + }, + "kubeConfig": { + "type": "string", + "description": "KubeConfig is the path where to find the remote workload cluster kube config." + }, + "namespace": { + "type": "string", + "description": "Namespace is the namespace where to sync the workloads into." + }, + "service": { + "type": "string", + "description": "Service is the vCluster service in the remote cluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalMultiNamespaceMode": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if multi namespace mode should get enabled" + }, + "namespaceLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "NamespaceLabels are extra labels that will be added by vCluster to each created namespace." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExperimentalSyncSettings": { + "properties": { + "disableSync": { + "type": "boolean", + "description": "DisableSync will not sync any resources and disable most control plane functionality." + }, + "rewriteKubernetesService": { + "type": "boolean", + "description": "RewriteKubernetesService will rewrite the kubernetes service to point to the vCluster if disableSync is enabled" + }, + "targetNamespace": { + "type": "string", + "description": "TargetNamespace is the namespace where the workloads should get synced to." + }, + "setOwner": { + "type": "boolean", + "description": "SetOwner specifies if vCluster should set an owner reference on the synced objects to the vCluster service. This allows for easy garbage collection." + }, + "syncLabels": { + "items": { + "type": "string" + }, + "type": "array", + "description": "SyncLabels are labels that should get not rewritten when syncing from vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Export": { + "properties": { + "apiVersion": { + "type": "string", + "description": "APIVersion of the object to sync" + }, + "kind": { + "type": "string", + "description": "Kind of the object to sync" + }, + "optional": { + "type": "boolean" + }, + "replaceOnConflict": { + "type": "boolean", + "description": "ReplaceWhenInvalid determines if the controller should try to recreate the object\nif there is a problem applying" + }, + "patches": { + "items": { + "$ref": "#/$defs/Patch" + }, + "type": "array", + "description": "Patches are the patches to apply on the virtual cluster objects\nwhen syncing them from the host cluster" + }, + "reversePatches": { + "items": { + "$ref": "#/$defs/Patch" + }, + "type": "array", + "description": "ReversePatches are the patches to apply to host cluster objects\nafter it has been synced to the virtual cluster" + }, + "selector": { + "$ref": "#/$defs/Selector", + "description": "Selector is a label selector to select the synced objects in the virtual cluster.\nIf empty, all objects will be synced." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExportKubeConfig": { + "properties": { + "context": { + "type": "string", + "description": "Context is the name of the context within the generated kube config to use." + }, + "server": { + "type": "string", + "description": "Server can be used to override the default https://localhost:8443 and specify a custom hostname for the\ngenerated kube-config." + }, + "secret": { + "$ref": "#/$defs/ExportKubeConfigSecretReference", + "description": "Secret defines in which secret in the host cluster the generated kube-config should be stored.\nIf this is not defined, vCluster will only create it at `vc-NAME`. If another name is specified here\nvCluster will also create the config in this other secret." + } + }, + "additionalProperties": false, + "type": "object", + "description": "ExportKubeConfig describes how vCluster should export the vCluster kube config" + }, + "ExportKubeConfigSecretReference": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the secret where the kube config should get stored." + }, + "namespace": { + "type": "string", + "description": "Namespace defines the namespace where the kube config secret should get stored. If this is not equal to the namespace\nwhere the vCluster is deployed, you need to make sure vCluster has access to this other namespace." + } + }, + "additionalProperties": false, + "type": "object", + "description": "ExportKubeConfigSecretReference defines in which secret in the host cluster the generated kube-config should be stored." + }, + "ExternalEtcd": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the external etcd should be used." + }, + "statefulSet": { + "$ref": "#/$defs/ExternalEtcdStatefulSet", + "description": "StatefulSet holds options for the external etcd statefulSet." + }, + "service": { + "$ref": "#/$defs/ExternalEtcdService", + "description": "Service holds options for the external etcd service." + }, + "headlessService": { + "$ref": "#/$defs/ExternalEtcdHeadlessService", + "description": "HeadlessService holds options for the external etcd headless service." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExternalEtcdHeadlessService": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the etcd headless service should be deployed" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for the external etcd headless service" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExternalEtcdHighAvailability": { + "properties": { + "replicas": { + "type": "integer", + "description": "Replicas are the amount of pods to use." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExternalEtcdService": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the etcd service should be deployed" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for the external etcd service" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ExternalEtcdStatefulSet": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the statefulSet should be deployed" + }, + "image": { + "$ref": "#/$defs/Image", + "description": "Image is the image to use for the external etcd statefulSet" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy for the external etcd image" + }, + "env": { + "items": { + "type": "object" + }, + "type": "array", + "description": "Env are extra environment variables" + }, + "extraArgs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ExtraArgs are appended to the etcd command." + }, + "resources": { + "$ref": "#/$defs/Resources", + "description": "Resources the etcd can consume" + }, + "pods": { + "$ref": "#/$defs/LabelsAndAnnotations", + "description": "Pods defines extra metadata for the etcd pods." + }, + "highAvailability": { + "$ref": "#/$defs/ExternalEtcdHighAvailability", + "description": "HighAvailability are high availability options" + }, + "scheduling": { + "$ref": "#/$defs/ControlPlaneScheduling", + "description": "Scheduling options for the etcd pods." + }, + "security": { + "$ref": "#/$defs/ControlPlaneSecurity", + "description": "Security options for the etcd pods." + }, + "persistence": { + "$ref": "#/$defs/ControlPlanePersistence", + "description": "Persistence options for the etcd pods." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Hook": { + "properties": { + "apiVersion": { + "type": "string", + "description": "APIVersion of the object to sync" + }, + "kind": { + "type": "string", + "description": "Kind of the object to sync" + }, + "verbs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Verbs are the verbs that the hook should mutate" + }, + "patches": { + "items": { + "$ref": "#/$defs/Patch" + }, + "type": "array", + "description": "Patches are the patches to apply on the object to be synced" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Hooks": { + "properties": { + "hostToVirtual": { + "items": { + "$ref": "#/$defs/Hook" + }, + "type": "array", + "description": "HostToVirtual is a hook that is executed before syncing from the host to the virtual cluster" + }, + "virtualToHost": { + "items": { + "$ref": "#/$defs/Hook" + }, + "type": "array", + "description": "VirtualToHost is a hook that is executed before syncing from the virtual to the host cluster" + } + }, + "additionalProperties": false, + "type": "object" + }, + "HostPathMapper": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if the host path mapper will be used" + }, + "central": { + "type": "boolean", + "description": "Central specifies if the central host path mapper will be used" + } + }, + "additionalProperties": false, + "type": "object" + }, + "IPBlock": { + "properties": { + "cidr": { + "type": "string", + "description": "cidr is a string representing the IPBlock\nValid examples are \"192.168.1.0/24\" or \"2001:db8::/64\"" + }, + "except": { + "items": { + "type": "string" + }, + "type": "array", + "description": "except is a slice of CIDRs that should not be included within an IPBlock\nValid examples are \"192.168.1.0/24\" or \"2001:db8::/64\"\nExcept values will be rejected if they are outside the cidr range\n+optional" + } + }, + "additionalProperties": false, + "type": "object", + "description": "IPBlock describes a particular CIDR (Ex." + }, + "Image": { + "properties": { + "repository": { + "type": "string", + "description": "Repository is the registry and repository of the container image, e.g. my-registry.com/my-repo/my-image" + }, + "tag": { + "type": "string", + "description": "Tag is the tag of the container image, e.g. latest" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Import": { + "properties": { + "apiVersion": { + "type": "string", + "description": "APIVersion of the object to sync" + }, + "kind": { + "type": "string", + "description": "Kind of the object to sync" + }, + "optional": { + "type": "boolean" + }, + "replaceOnConflict": { + "type": "boolean", + "description": "ReplaceWhenInvalid determines if the controller should try to recreate the object\nif there is a problem applying" + }, + "patches": { + "items": { + "$ref": "#/$defs/Patch" + }, + "type": "array", + "description": "Patches are the patches to apply on the virtual cluster objects\nwhen syncing them from the host cluster" + }, + "reversePatches": { + "items": { + "$ref": "#/$defs/Patch" + }, + "type": "array", + "description": "ReversePatches are the patches to apply to host cluster objects\nafter it has been synced to the virtual cluster" + } + }, + "additionalProperties": false, + "type": "object" + }, + "LabelSelectorRequirement": { + "properties": { + "key": { + "type": "string", + "description": "key is the label key that the selector applies to." + }, + "operator": { + "type": "string", + "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist." + }, + "values": { + "items": { + "type": "string" + }, + "type": "array", + "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch." + } + }, + "additionalProperties": false, + "type": "object" + }, + "LabelsAndAnnotations": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "LimitRange": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the limit range should be deployed by vCluster." + }, + "default": { + "type": "object", + "description": "Default are the default limits for the limit range" + }, + "defaultRequest": { + "type": "object", + "description": "DefaultRequest are the default request options for the limit range" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "LocalObjectReference": { + "properties": { + "name": { + "type": "string", + "description": "Name of the referent.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" + } + }, + "additionalProperties": false, + "type": "object", + "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace." + }, + "MetricsProxy": { + "properties": { + "nodes": { + "type": "boolean", + "description": "Nodes defines if metrics-server nodes api should get proxied from host to vCluster." + }, + "pods": { + "type": "boolean", + "description": "Pods defines if metrics-server pods api should get proxied from host to vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "NetworkPolicy": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the network policy should be deployed by vCluster." + }, + "fallbackDns": { + "type": "string" + }, + "outgoingConnections": { + "$ref": "#/$defs/OutgoingConnections" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "NetworkProxyKubelets": { + "properties": { + "byHostname": { + "type": "boolean", + "description": "ByHostname will add a special vCluster hostname to the nodes where the node can be reached at. This doesn't work\nfor all applications, e.g. prometheus requires a node ip." + }, + "byIP": { + "type": "boolean", + "description": "ByIP will create a separate service in the host cluster for every node that will point to vCluster and will be used to\nroute traffic." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Networking": { + "properties": { + "replicateServices": { + "$ref": "#/$defs/ReplicateServices", + "description": "ReplicateServices allows replicating services from the host within the vCluster or the other way around." + }, + "resolveDNS": { + "items": { + "$ref": "#/$defs/ResolveDNS" + }, + "type": "array", + "description": "ResolveDNS allows to define extra DNS rules. This only works if embedded coredns is configured." + }, + "advanced": { + "$ref": "#/$defs/NetworkingAdvanced", + "description": "Advanced holds advanced network options." + } + }, + "additionalProperties": false, + "type": "object" + }, + "NetworkingAdvanced": { + "properties": { + "clusterDomain": { + "type": "string", + "description": "ClusterDomain is the Kubernetes cluster domain to use within the vCluster." + }, + "fallbackHostCluster": { + "type": "boolean", + "description": "FallbackHostCluster allows to fallback dns to the host cluster. This is useful if you want to reach host services without\nany other modification. You will need to provide a namespace for the service, e.g. my-other-service.my-other-namespace" + }, + "proxyKubelets": { + "$ref": "#/$defs/NetworkProxyKubelets", + "description": "ProxyKubelets allows rewriting certain metrics and stats from the Kubelet to \"fake\" this for applications such as\nprometheus or other node exporters." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Observability": { + "properties": { + "metrics": { + "$ref": "#/$defs/ObservabilityMetrics", + "description": "Metrics allows to proxy metrics server apis from host to vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ObservabilityMetrics": { + "properties": { + "proxy": { + "$ref": "#/$defs/MetricsProxy", + "description": "Proxy holds the configuration what metrics-server apis should get proxied." + } + }, + "additionalProperties": false, + "type": "object" + }, + "OutgoingConnections": { + "properties": { + "ipBlock": { + "$ref": "#/$defs/IPBlock" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Patch": { + "properties": { + "op": { + "type": "string", + "description": "Operation is the type of the patch" + }, + "fromPath": { + "type": "string", + "description": "FromPath is the path from the other object" + }, + "path": { + "type": "string", + "description": "Path is the path of the patch" + }, + "namePath": { + "type": "string", + "description": "NamePath is the path to the name of a child resource within Path" + }, + "namespacePath": { + "type": "string", + "description": "NamespacePath is path to the namespace of a child resource within Path" + }, + "value": { + "description": "Value is the new value to be set to the path" + }, + "regex": { + "type": "string", + "description": "Regex - is regular expresion used to identify the Name,\nand optionally Namespace, parts of the field value that\nwill be replaced with the rewritten Name and/or Namespace" + }, + "conditions": { + "items": { + "$ref": "#/$defs/PatchCondition" + }, + "type": "array", + "description": "Conditions are conditions that must be true for\nthe patch to get executed" + }, + "ignore": { + "type": "boolean", + "description": "Ignore determines if the path should be ignored if handled as a reverse patch" + }, + "sync": { + "$ref": "#/$defs/PatchSync", + "description": "Sync defines if a specialized syncer should be initialized using values\nfrom the rewriteName operation as Secret/Configmap names to be synced" + } + }, + "additionalProperties": false, + "type": "object" + }, + "PatchCondition": { + "properties": { + "path": { + "type": "string", + "description": "Path is the path within the object to select" + }, + "subPath": { + "type": "string", + "description": "SubPath is the path below the selected object to select" + }, + "equal": { + "description": "Equal is the value the path should be equal to" + }, + "notEqual": { + "description": "NotEqual is the value the path should not be equal to" + }, + "empty": { + "type": "boolean", + "description": "Empty means that the path value should be empty or unset" + } + }, + "additionalProperties": false, + "type": "object" + }, + "PatchSync": { + "properties": { + "secret": { + "type": "boolean" + }, + "configmap": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Platform": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the vCluster instance in the vCluster platform" + }, + "owner": { + "$ref": "#/$defs/PlatformOwner", + "description": "Owner is the desired owner of the vCluster within the vCluster platform. If empty will take the current user." + }, + "project": { + "type": "string", + "description": "Project is the project within the platform where the vCluster should connect to." + }, + "apiKey": { + "$ref": "#/$defs/PlatformAPIKey", + "description": "APIKey defines how vCluster can find the api key used for the platform." + } + }, + "additionalProperties": false, + "type": "object" + }, + "PlatformAPIKey": { + "properties": { + "value": { + "type": "string", + "description": "Value specifies the api key as a regular text value." + }, + "secretRef": { + "$ref": "#/$defs/PlatformAPIKeySecretReference", + "description": "SecretRef defines where to find the platform api key. By default vCluster will search in the following locations in this precedence:\n* platform.apiKey.value\n* environment variable called LICENSE\n* secret specified under platform.secret.name\n* secret called \"vcluster-platform-api-key\" in the vCluster namespace" + } + }, + "additionalProperties": false, + "type": "object" + }, + "PlatformAPIKeySecretReference": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the secret where the platform api key is stored. This defaults to vcluster-platform-api-key if undefined." + }, + "namespace": { + "type": "string", + "description": "Namespace defines the namespace where the api key secret should be retrieved from. If this is not equal to the namespace\nwhere the vCluster is deployed, you need to make sure vCluster has access to this other namespace." + } + }, + "additionalProperties": false, + "type": "object", + "description": "PlatformAPIKeySecretReference defines where to find the platform api key." + }, + "PlatformOwner": { + "properties": { + "user": { + "type": "string", + "description": "User is the user id within the platform. This is mutually exclusive with team." + }, + "team": { + "type": "string", + "description": "Team is the team id within the platform. This is mutually exclusive with user." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Plugin": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the init-container and NOT the plugin name" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command that should be used for the init container" + }, + "args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Args are the arguments that should be used for the init container" + }, + "image": { + "type": "string", + "description": "Image is the container image that should be used for the plugin" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy to use for the container image" + }, + "config": { + "type": "object", + "description": "Config is the plugin config to use. This can be arbitrary config used for the plugin." + }, + "securityContext": { + "type": "object", + "description": "SecurityContext is the container security context used for the init container" + }, + "resources": { + "type": "object", + "description": "Resources are the container resources used for the init container" + }, + "volumeMounts": { + "items": true, + "type": "array", + "description": "VolumeMounts are extra volume mounts for the init container" + }, + "rbac": { + "$ref": "#/$defs/PluginsRBAC", + "description": "RBAC holds additional rbac configuration for the plugin" + }, + "version": { + "type": "string", + "description": "Version is the plugin version, this is only needed for legacy plugins." + }, + "env": { + "items": true, + "type": "array" + }, + "envFrom": { + "items": true, + "type": "array" + }, + "lifecycle": { + "type": "object" + }, + "livenessProbe": { + "type": "object" + }, + "readinessProbe": { + "type": "object" + }, + "startupProbe": { + "type": "object" + }, + "workingDir": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Plugins": { + "properties": { + "name": { + "type": "string", + "description": "Name is the name of the init-container and NOT the plugin name" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Command is the command that should be used for the init container" + }, + "args": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Args are the arguments that should be used for the init container" + }, + "image": { + "type": "string", + "description": "Image is the container image that should be used for the plugin" + }, + "imagePullPolicy": { + "type": "string", + "description": "ImagePullPolicy is the pull policy to use for the container image" + }, + "config": { + "type": "object", + "description": "Config is the plugin config to use. This can be arbitrary config used for the plugin." + }, + "securityContext": { + "type": "object", + "description": "SecurityContext is the container security context used for the init container" + }, + "resources": { + "type": "object", + "description": "Resources are the container resources used for the init container" + }, + "volumeMounts": { + "items": true, + "type": "array", + "description": "VolumeMounts are extra volume mounts for the init container" + }, + "rbac": { + "$ref": "#/$defs/PluginsRBAC", + "description": "RBAC holds additional rbac configuration for the plugin" + } + }, + "additionalProperties": false, + "type": "object" + }, + "PluginsExtraRules": { + "properties": { + "extraRules": { + "items": { + "$ref": "#/$defs/RBACPolicyRule" + }, + "type": "array", + "description": "ExtraRules are extra rbac permissions roles that will be added to role or cluster role" + } + }, + "additionalProperties": false, + "type": "object" + }, + "PluginsRBAC": { + "properties": { + "role": { + "$ref": "#/$defs/PluginsExtraRules", + "description": "Role holds extra vCluster role permissions for the plugin" + }, + "clusterRole": { + "$ref": "#/$defs/PluginsExtraRules", + "description": "ClusterRole holds extra vCluster cluster role permissions required for the plugin" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Policies": { + "properties": { + "podSecurityStandard": { + "type": "string", + "description": "PodSecurityStandard that can be enforced can be one of: empty (\"\"), baseline, restricted or privileged" + }, + "resourceQuota": { + "$ref": "#/$defs/ResourceQuota", + "description": "ResourceQuota specifies resource quota options." + }, + "limitRange": { + "$ref": "#/$defs/LimitRange", + "description": "LimitRange specifies limit range options." + }, + "networkPolicy": { + "$ref": "#/$defs/NetworkPolicy", + "description": "NetworkPolicy specifies network policy options." + }, + "centralAdmission": { + "$ref": "#/$defs/CentralAdmission", + "description": "CentralAdmission defines what validating or mutating webhooks should be enforced within the vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "RBAC": { + "properties": { + "role": { + "$ref": "#/$defs/RBACRole", + "description": "Role holds vCluster role configuration" + }, + "clusterRole": { + "$ref": "#/$defs/RBACClusterRole", + "description": "ClusterRole holds vCluster cluster role configuration" + } + }, + "additionalProperties": false, + "type": "object" + }, + "RBACClusterRole": { + "properties": { + "disabled": { + "type": "boolean", + "description": "Disabled defines if the cluster role should be disabled. Otherwise, its automatically determined if vCluster requires a cluster role." + }, + "overwriteRules": { + "items": { + "type": "object" + }, + "type": "array", + "description": "OverwriteRules will overwrite the cluster role rules completely." + }, + "extraRules": { + "items": { + "type": "object" + }, + "type": "array", + "description": "ExtraRules will add rules to the cluster role." + } + }, + "additionalProperties": false, + "type": "object" + }, + "RBACPolicyRule": { + "properties": { + "verbs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs." + }, + "apiGroups": { + "items": { + "type": "string" + }, + "type": "array", + "description": "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of\nthe enumerated resources in any API group will be allowed. \"\" represents the core API group and \"*\" represents all API groups." + }, + "resources": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Resources is a list of resources this rule applies to. '*' represents all resources." + }, + "resourceNames": { + "items": { + "type": "string" + }, + "type": "array", + "description": "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed." + }, + "nonResourceURLs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path\nSince non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.\nRules can either apply to API resources (such as \"pods\" or \"secrets\") or non-resource URL paths (such as \"/api\"), but not both." + } + }, + "additionalProperties": false, + "type": "object" + }, + "RBACRole": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled" + }, + "overwriteRules": { + "items": { + "type": "object" + }, + "type": "array", + "description": "OverwriteRules will overwrite the role rules completely." + }, + "extraRules": { + "items": { + "type": "object" + }, + "type": "array", + "description": "ExtraRules will add rules to the role." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ReplicateServices": { + "properties": { + "toHost": { + "items": { + "$ref": "#/$defs/ServiceMapping" + }, + "type": "array", + "description": "ToHost defines the services that should get synced from vCluster to the host cluster. If services are\nsynced to a different namespace than the vCluster is in, additional permissions for the other namespace\nare required." + }, + "fromHost": { + "items": { + "$ref": "#/$defs/ServiceMapping" + }, + "type": "array", + "description": "FromHost defines the services that should get synced from the host to the vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ResolveDNS": { + "properties": { + "hostname": { + "type": "string" + }, + "service": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "target": { + "$ref": "#/$defs/ResolveDNSTarget" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ResolveDNSTarget": { + "properties": { + "hostname": { + "type": "string" + }, + "vcluster": { + "type": "string" + }, + "service": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "mode": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ResourceQuota": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if the resource quota should be enabled." + }, + "quota": { + "type": "object", + "description": "Quota are the quota options" + }, + "scopeSelector": { + "$ref": "#/$defs/ScopeSelector", + "description": "ScopeSelector is the resource quota scope selector" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Scopes are the resource quota scopes" + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are extra annotations for this resource." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are extra labels for this resource." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Resources": { + "properties": { + "limits": { + "type": "object", + "description": "Limits are resource limits for the container" + }, + "requests": { + "type": "object", + "description": "Requests are minimal resources that will be consumed by the container" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ScopeSelector": { + "properties": { + "matchExpressions": { + "items": { + "$ref": "#/$defs/LabelSelectorRequirement" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Selector": { + "properties": { + "labelSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "LabelSelector are the labels to select the object from" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ServiceMapping": { + "properties": { + "from": { + "type": "string", + "description": "From is the service that should get synced. Can be either in the form name or namespace/name." + }, + "to": { + "type": "string", + "description": "To is the target service that it should get synced to. Can be either in the form name or namespace/name." + } + }, + "additionalProperties": false, + "type": "object" + }, + "ServiceMonitor": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled configures if helm should create the service monitor." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are the extra labels to add to the service monitor." + }, + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Annotations are the extra annotations to add to the service monitor." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Sync": { + "properties": { + "toHost": { + "$ref": "#/$defs/SyncToHost", + "description": "ToHost configures what resources should get synced from the vCluster to the host cluster." + }, + "fromHost": { + "$ref": "#/$defs/SyncFromHost", + "description": "FromHost configures what resources should get purely synced from the host cluster to the vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncAllResource": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if this option should be enabled." + }, + "all": { + "type": "boolean", + "description": "All defines if all resources of that type should get synced or only the necessary ones that are needed." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncFromHost": { + "properties": { + "csiDrivers": { + "$ref": "#/$defs/EnableSwitch", + "description": "CSIDrivers defines if csi drivers should get synced from the host cluster to the vCluster, but not back." + }, + "csiNodes": { + "$ref": "#/$defs/EnableSwitch", + "description": "CSINodes defines if csi nodes should get synced from the host cluster to the vCluster, but not back." + }, + "csiStorageCapacities": { + "$ref": "#/$defs/EnableSwitch", + "description": "CSIStorageCapacities defines if csi storage capacities should get synced from the host cluster to the vCluster, but not back." + }, + "ingressClasses": { + "$ref": "#/$defs/EnableSwitch", + "description": "IngressClasses defines if ingress classes should get synced from the host cluster to the vCluster, but not back." + }, + "events": { + "$ref": "#/$defs/EnableSwitch", + "description": "Events defines if events should get synced from the host cluster to the vCluster, but not back." + }, + "storageClasses": { + "$ref": "#/$defs/EnableSwitch", + "description": "StorageClasses defines if storage classes should get synced from the host cluster to the vCluster, but not back." + }, + "nodes": { + "$ref": "#/$defs/SyncNodes", + "description": "Nodes defines if nodes should get synced from the host cluster to the vCluster, but not back." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncNodeSelector": { + "properties": { + "all": { + "type": "boolean", + "description": "All specifies if all nodes should get synced by vCluster from the host to the vCluster or only the ones where pods are assigned to." + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "Labels are the node labels used to sync nodes from host cluster to vCluster. This will also set the node selector when syncing a pod from vCluster to host cluster to the same value." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncNodes": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if syncing real nodes should be enabled. If this is disabled, vCluster will create fake nodes instead." + }, + "syncBackChanges": { + "type": "boolean", + "description": "SyncBackChanges enables syncing labels and taints from the vCluster to the host cluster. If this is enabled someone within the vCluster will be able to change the labels and taints of the host cluster node." + }, + "clearImageStatus": { + "type": "boolean", + "description": "ClearImageStatus will erase the image status when syncing a node. This allows to hide images that are pulled by the node." + }, + "selector": { + "$ref": "#/$defs/SyncNodeSelector", + "description": "Selector can be used to define more granular what nodes should get synced from the host cluster to the vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncPods": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled defines if pod syncing should be enabled." + }, + "translateImage": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "description": "TranslateImage maps an image to another image that should be used instead. For example this can be used to rewrite\na certain image that is used within the vCluster to be another image on the host cluster" + }, + "enforceTolerations": { + "items": { + "type": "string" + }, + "type": "array", + "description": "EnforceTolerations will add the specified tolerations to all pods synced by the vCluster." + }, + "useSecretsForSATokens": { + "type": "boolean", + "description": "UseSecretsForSATokens will use secrets to save the generated service account tokens by vCluster instead of using a\npod annotation." + }, + "rewriteHosts": { + "$ref": "#/$defs/SyncRewriteHosts", + "description": "RewriteHosts is a special option needed to rewrite statefulset containers to allow the correct FQDN. vCluster will add\na small container to each stateful set pod that will initially rewrite the /etc/hosts file to match the FQDN expected by\nthe vCluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncRewriteHosts": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled specifies if rewriting stateful set pods should be enabled." + }, + "initContainerImage": { + "type": "string", + "description": "InitContainerImage is the image vCluster should use to rewrite this FQDN." + } + }, + "additionalProperties": false, + "type": "object" + }, + "SyncToHost": { + "properties": { + "services": { + "$ref": "#/$defs/EnableSwitch", + "description": "Services defines if services created within the vCluster should get synced to the host cluster." + }, + "endpoints": { + "$ref": "#/$defs/EnableSwitch", + "description": "Endpoints defines if endpoints created within the vCluster should get synced to the host cluster." + }, + "ingresses": { + "$ref": "#/$defs/EnableSwitch", + "description": "Ingresses defines if ingresses created within the vCluster should get synced to the host cluster." + }, + "priorityClasses": { + "$ref": "#/$defs/EnableSwitch", + "description": "PriorityClasses defines if priority classes created within the vCluster should get synced to the host cluster." + }, + "networkPolicies": { + "$ref": "#/$defs/EnableSwitch", + "description": "NetworkPolicies defines if network policies created within the vCluster should get synced to the host cluster." + }, + "volumeSnapshots": { + "$ref": "#/$defs/EnableSwitch", + "description": "VolumeSnapshots defines if volume snapshots created within the vCluster should get synced to the host cluster." + }, + "podDisruptionBudgets": { + "$ref": "#/$defs/EnableSwitch", + "description": "PodDisruptionBudgets defines if pod disruption budgets created within the vCluster should get synced to the host cluster." + }, + "serviceAccounts": { + "$ref": "#/$defs/EnableSwitch", + "description": "ServiceAccounts defines if service accounts created within the vCluster should get synced to the host cluster." + }, + "storageClasses": { + "$ref": "#/$defs/EnableSwitch", + "description": "StorageClasses defines if storage classes created within the vCluster should get synced to the host cluster." + }, + "persistentVolumes": { + "$ref": "#/$defs/EnableSwitch", + "description": "PersistentVolumes defines if persistent volumes created within the vCluster should get synced to the host cluster." + }, + "persistentVolumeClaims": { + "$ref": "#/$defs/EnableSwitch", + "description": "PersistentVolumeClaims defines if persistent volume claims created within the vCluster should get synced to the host cluster." + }, + "configMaps": { + "$ref": "#/$defs/SyncAllResource", + "description": "ConfigMaps defines if config maps created within the vCluster should get synced to the host cluster." + }, + "secrets": { + "$ref": "#/$defs/SyncAllResource", + "description": "Secrets defines if secrets created within the vCluster should get synced to the host cluster." + }, + "pods": { + "$ref": "#/$defs/SyncPods", + "description": "Pods defines if pods created within the vCluster should get synced to the host cluster." + } + }, + "additionalProperties": false, + "type": "object" + }, + "Telemetry": { + "properties": { + "disabled": { + "type": "boolean", + "description": "Disabled specifies that the telemetry for vCluster control plane should be disabled." + }, + "instanceCreator": { + "type": "string" + }, + "platformUserID": { + "type": "string" + }, + "platformInstanceID": { + "type": "string" + }, + "machineID": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "VirtualClusterKubeConfig": { + "properties": { + "kubeConfig": { + "type": "string", + "description": "KubeConfig is the virtual cluster kube config path." + }, + "serverCAKey": { + "type": "string", + "description": "ServerCAKey is the server ca key path." + }, + "serverCACert": { + "type": "string", + "description": "ServerCAKey is the server ca cert path." + }, + "clientCACert": { + "type": "string", + "description": "ServerCAKey is the client ca cert path." + }, + "requestHeaderCACert": { + "type": "string", + "description": "RequestHeaderCACert is the request header ca cert path." + } + }, + "additionalProperties": false, + "type": "object" + }, + "VolumeClaim": { + "properties": { + "disabled": { + "type": "boolean", + "description": "Disabled signals to disable deploying a persistent volume claim. If false, vCluster will automatically determine\nbased on the chosen distro and other options if this is required." + }, + "accessModes": { + "items": { + "type": "string" + }, + "type": "array", + "description": "AccessModes are the persistent volume claim access modes." + }, + "retentionPolicy": { + "type": "string", + "description": "RetentionPolicy is the persistent volume claim retention policy." + }, + "size": { + "type": "string", + "description": "Size is the persistent volume claim storage size." + }, + "storageClass": { + "type": "string", + "description": "StorageClass is the persistent volume claim storage class." + } + }, + "additionalProperties": false, + "type": "object" + }, + "VolumeMount": { + "properties": { + "name": { + "type": "string", + "description": "This must match the Name of a Volume." + }, + "readOnly": { + "type": "boolean", + "description": "Mounted read-only if true, read-write otherwise (false or unspecified).\nDefaults to false." + }, + "mountPath": { + "type": "string", + "description": "Path within the container at which the volume should be mounted. Must\nnot contain ':'." + }, + "subPath": { + "type": "string", + "description": "Path within the volume from which the container's volume should be mounted.\nDefaults to \"\" (volume's root)." + }, + "mountPropagation": { + "type": "string", + "description": "mountPropagation determines how mounts are propagated from the host\nto container and the other way around.\nWhen not set, MountPropagationNone is used.\nThis field is beta in 1.10." + }, + "subPathExpr": { + "type": "string", + "description": "Expanded path within the volume from which the container's volume should be mounted.\nBehaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.\nDefaults to \"\" (volume's root).\nSubPathExpr and SubPath are mutually exclusive." + } + }, + "additionalProperties": false, + "type": "object", + "description": "VolumeMount describes a mounting of a Volume within a container." + } + }, + "properties": { + "exportKubeConfig": { + "$ref": "#/$defs/ExportKubeConfig", + "description": "ExportKubeConfig describes how vCluster should export the vCluster kube config" + }, + "controlPlane": { + "$ref": "#/$defs/ControlPlane", + "description": "ControlPlane holds options how to configure the vCluster control-plane" + }, + "sync": { + "$ref": "#/$defs/Sync", + "description": "Sync describes how to sync resources from the vCluster to host cluster and back" + }, + "observability": { + "$ref": "#/$defs/Observability", + "description": "Observability holds options to proxy metrics from the host cluster into the vCluster" + }, + "networking": { + "$ref": "#/$defs/Networking", + "description": "Networking are networking options related to the vCluster" + }, + "plugins": { + "anyOf": [ + { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "object" + }, + { + "type": "object" + } + ], + "additionalProperties": { + "$ref": "#/$defs/Plugins" + }, + "description": "Plugins define what vCluster plugins to load." + }, + "policies": { + "$ref": "#/$defs/Policies", + "description": "Policies defines policies to enforce for the vCluster deployment as well as within the vCluster" + }, + "rbac": { + "$ref": "#/$defs/RBAC", + "description": "RBAC are role based access control options for the vCluster" + }, + "experimental": { + "$ref": "#/$defs/Experimental", + "description": "Experimental are alpha features for vCluster. Configuration here might change, so be careful with this." + }, + "telemetry": { + "$ref": "#/$defs/Telemetry", + "description": "Telemetry is the configuration related to telemetry gathered about vCluster usage." + }, + "platform": { + "$ref": "#/$defs/Platform", + "description": "Platform holds options how vCluster should connect to vCluster platform." + }, + "serviceCIDR": { + "type": "string", + "description": "ServiceCIDR holds the service cidr for the vCluster. Please do not use that option anymore." + }, + "pro": { + "type": "boolean", + "description": "Pro specifies if vCluster pro should be used. This is automatically inferred in newer versions. Please do not use that option anymore." + }, + "plugin": { + "anyOf": [ + { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "object" + }, + { + "type": "object" + } + ], + "additionalProperties": { + "$ref": "#/$defs/Plugin" + }, + "description": "Plugin specifies what vCluster plugins to enable. Please use \"plugins\" instead. Please do not use that option anymore." + } + }, + "additionalProperties": false, + "type": "object", + "description": "Config is the vCluster config." +} \ No newline at end of file diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 000000000..4fe0efe60 --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,508 @@ +# Sync Options +sync: + toHost: + services: + enabled: true + endpoints: + enabled: true + persistentVolumeClaims: + enabled: true + configMaps: + enabled: true + all: false + secrets: + enabled: true + all: false + pods: + enabled: true + translateImage: {} + enforceTolerations: [] + useSecretsForSATokens: false + rewriteHosts: + enabled: true + initContainerImage: "library/alpine:3.13.1" + ingresses: + enabled: false + priorityClasses: + enabled: false + networkPolicies: + enabled: false + volumeSnapshots: + enabled: false + podDisruptionBudgets: + enabled: false + serviceAccounts: + enabled: false + storageClasses: + enabled: false + persistentVolumes: + enabled: false + + fromHost: + events: + enabled: true + csiDrivers: + enabled: false + csiNodes: + enabled: false + csiStorageCapacities: + enabled: false + ingressClasses: + enabled: false + storageClasses: + enabled: false + nodes: + enabled: false + syncBackChanges: false + clearImageStatus: false + selector: + all: false + labels: {} + +# Control Plane Options +controlPlane: + # What distro to use for vCluster, if none is specified, k3s is used + distro: + k3s: + enabled: false + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "rancher/k3s" + tag: "v1.29.0-k3s1" + securityContext: {} + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 40m + memory: 64Mi + + k0s: + enabled: false + config: "" + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "k0sproject/k0s" + tag: "v1.29.1-k0s.0" + securityContext: {} + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 40m + memory: 64Mi + + k8s: + enabled: false + apiServer: + disabled: false + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "registry.k8s.io/kube-apiserver" + tag: "v1.29.0" + controllerManager: + disabled: false + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "registry.k8s.io/kube-controller-manager" + tag: "v1.29.0" + scheduler: + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "registry.k8s.io/kube-scheduler" + tag: "v1.29.0" + env: [] + securityContext: {} + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 40m + memory: 64Mi + + eks: + enabled: false + apiServer: + disabled: false + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "public.ecr.aws/eks-distro/kubernetes/kube-apiserver" + tag: "v1.28.2-eks-1-28-6" + controllerManager: + disabled: false + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager" + tag: "v1.28.2-eks-1-28-6" + scheduler: + command: [] + extraArgs: [] + imagePullPolicy: "" + image: + repository: "public.ecr.aws/eks-distro/kubernetes/kube-scheduler" + tag: "v1.28.2-eks-1-28-6" + env: [] + securityContext: {} + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 40m + memory: 64Mi + + backingStore: + embeddedEtcd: + enabled: false + migrateFromExternalEtcd: false + externalEtcd: + enabled: false + statefulSet: + enabled: true + annotations: {} + labels: {} + image: + repository: "registry.k8s.io/etcd" + tag: "3.5.10-0" + imagePullPolicy: "" + extraArgs: [] + env: [] + resources: + requests: + cpu: 20m + memory: 150Mi + pods: + annotations: {} + labels: {} + highAvailability: + replicas: 1 + scheduling: + podManagementPolicy: Parallel + nodeSelector: {} + affinity: {} + tolerations: [] + topologySpreadConstraints: [] + priorityClassName: "" + security: + podSecurityContext: {} + containerSecurityContext: {} + persistence: + volumeClaim: + disabled: false + # Defines if the PVC should get automatically deleted when the StatefulSet is deleted. Can be either Delete or Retain + retentionPolicy: Retain + size: 5Gi + storageClass: "" + accessModes: [ "ReadWriteOnce" ] + volumeClaimTemplates: [] + addVolumes: [] + addVolumeMounts: [] + service: + enabled: true + annotations: {} + headlessService: + enabled: true + annotations: {} + + proxy: + bindAddress: "0.0.0.0" + port: 8443 + extraSANs: [] + + coredns: + enabled: true + embedded: false + overwriteManifests: "" + overwriteConfig: "" + + service: + annotations: {} + labels: {} + spec: + type: ClusterIP + + deployment: + annotations: {} + labels: {} + image: "" + replicas: 1 + pods: + labels: {} + annotations: {} + nodeSelector: {} + resources: + limits: + cpu: 1000m + memory: 170Mi + requests: + cpu: 20m + memory: 64Mi + + service: + enabled: true + labels: {} + annotations: {} + kubeletNodePort: 0 + httpsNodePort: 0 + spec: + type: ClusterIP + + ingress: + enabled: false + host: "my-host.com" + pathType: ImplementationSpecific + labels: {} + annotations: + nginx.ingress.kubernetes.io/backend-protocol: HTTPS + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + spec: + tls: [] + + statefulSet: + labels: {} + annotations: {} + imagePullPolicy: "" + image: + repository: "ghcr.io/loft-sh/vcluster" + tag: "" + + workingDir: "" + command: [] + args: [] + env: [] + + resources: + limits: + ephemeral-storage: 8Gi + memory: 2Gi + requests: + ephemeral-storage: 200Mi + cpu: 200m + memory: 256Mi + + pods: + labels: {} + annotations: {} + + highAvailability: + replicas: 1 + leaseDuration: 60 + renewDeadline: 40 + retryPeriod: 15 + + security: + podSecurityContext: {} + containerSecurityContext: + allowPrivilegeEscalation: false + runAsUser: 0 + runAsGroup: 0 + + persistence: + volumeClaim: + disabled: false + # Defines if the PVC should get automatically deleted when the StatefulSet is deleted. Can be either Delete or Retain + retentionPolicy: Retain + size: 5Gi + storageClass: "" + accessModes: [ "ReadWriteOnce" ] + volumeClaimTemplates: [] + addVolumeMounts: [] + addVolumes: [] + + scheduling: + podManagementPolicy: Parallel + topologySpreadConstraints: [] + priorityClassName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + + probes: + livenessProbe: + enabled: true + readinessProbe: + enabled: true + startupProbe: + enabled: true + + serviceMonitor: + enabled: false + labels: {} + annotations: {} + + advanced: + defaultImageRegistry: "" + + virtualScheduler: + enabled: false + + serviceAccount: + enabled: true + name: "" + imagePullSecrets: [] + labels: {} + annotations: {} + + workloadServiceAccount: + enabled: true + name: "" + imagePullSecrets: [] + annotations: {} + labels: {} + + headlessService: + labels: {} + annotations: {} + + globalMetadata: + annotations: {} + +rbac: + role: + enabled: true + overwriteRules: [] + extraRules: [] + clusterRole: + disabled: false + overwriteRules: [] + extraRules: [] + +observability: + metrics: + proxy: + nodes: false + pods: false + +networking: + # Embedded CoreDNS plugin config + replicateServices: + toHost: [] + fromHost: [] + resolveDNS: [] + advanced: + clusterDomain: "cluster.local" + fallbackHostCluster: true + proxyKubelets: + byHostname: true + byIP: true + +policies: + resourceQuota: + enabled: false + labels: {} + annotations: {} + quota: + requests.cpu: 10 + requests.memory: 20Gi + requests.storage: "100Gi" + requests.ephemeral-storage: 60Gi + limits.cpu: 20 + limits.memory: 40Gi + limits.ephemeral-storage: 160Gi + services.nodeports: 0 + services.loadbalancers: 1 + count/endpoints: 40 + count/pods: 20 + count/services: 20 + count/secrets: 100 + count/configmaps: 100 + count/persistentvolumeclaims: 20 + scopeSelector: + matchExpressions: [] + scopes: [] + + limitRange: + enabled: false + labels: {} + annotations: {} + default: + ephemeral-storage: 8Gi + memory: 512Mi + cpu: "1" + defaultRequest: + ephemeral-storage: 3Gi + memory: 128Mi + cpu: 100m + + networkPolicy: + enabled: false + labels: {} + annotations: {} + fallbackDns: 8.8.8.8 + outgoingConnections: + ipBlock: + cidr: 0.0.0.0/0 + except: + - 100.64.0.0/10 + - 127.0.0.0/8 + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + + centralAdmission: + validatingWebhooks: [] + mutatingWebhooks: [] + +# Export vCluster Kube Config +exportKubeConfig: + context: "" + server: "" + secret: + name: "" + namespace: "" + +# What plugins should get used +plugins: {} + +# Functionality that is likely to change, use with caution! +experimental: + multiNamespaceMode: + enabled: false + + syncSettings: + disableSync: false + rewriteKubernetesService: false + targetNamespace: "" + setOwner: true + + isolatedControlPlane: + headless: false + + deploy: + manifests: "" + manifestsTemplate: '' + helm: [] + + genericSync: + clusterRole: + extraRules: [] + role: + extraRules: [] + +platform: + apiKey: + value: "" + secretRef: + name: "" + namespace: "" + +telemetry: + disabled: false diff --git a/charts/eks/.helmignore b/charts/eks/.helmignore deleted file mode 100644 index 3b2afc666..000000000 --- a/charts/eks/.helmignore +++ /dev/null @@ -1,25 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. - -.DS_Store - -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ - -# Common backup files -*.swp -*.bak -*.tmp -*~ - -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/charts/eks/Chart.yaml b/charts/eks/Chart.yaml deleted file mode 100644 index 53f33240f..000000000 --- a/charts/eks/Chart.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v2 -name: vcluster-eks -description: vcluster - Virtual Kubernetes Clusters (eks) -home: https://vcluster.com -icon: https://static.loft.sh/branding/logos/vcluster/vertical/vcluster_vertical.svg -keywords: - - developer - - development - - sharing - - share - - multi-tenancy - - tenancy - - cluster - - space - - namespace - - vcluster - - vclusters -maintainers: - - name: Loft Labs, Inc. - email: info@loft.sh - url: https://twitter.com/loft_sh -sources: - - https://github.com/loft-sh/vcluster -type: application - -version: 0.0.1 # version is auto-generated by release pipeline diff --git a/charts/eks/README.md b/charts/eks/README.md deleted file mode 100644 index 6541ee835..000000000 --- a/charts/eks/README.md +++ /dev/null @@ -1,64 +0,0 @@ - -# vcluster (eks) - -## **[GitHub](https://github.com/loft-sh/vcluster)** • **[Website](https://www.vcluster.com)** • **[Quickstart](https://www.vcluster.com/docs/getting-started/setup)** • **[Documentation](https://www.vcluster.com/docs/what-are-virtual-clusters)** • **[Blog](https://loft.sh/blog)** • **[Twitter](https://twitter.com/loft_sh)** • **[Slack](https://slack.loft.sh/)** - -Create fully functional virtual Kubernetes clusters - Each vcluster runs inside a namespace of the underlying k8s cluster. It's cheaper than creating separate full-blown clusters and it offers better multi-tenancy and isolation than regular namespaces. - -## Prerequisites - -- Kubernetes 1.18+ -- Helm 3+ - -## Get Helm Repository Info - -```bash -helm repo add loft-sh https://charts.loft.sh -helm repo update -``` - -See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation. - -## Install Helm Chart - -```bash -helm upgrade [RELEASE_NAME] loft-sh/vcluster-eks -n [RELEASE_NAMESPACE] --create-namespace --install -``` - -See [vcluster docs](https://vcluster.com/docs) for configuration options. - -See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation. - -## Connect to the vcluster - -In order to connect to the installed vcluster, please install [vcluster cli](https://www.vcluster.com/docs/getting-started/setup) and run: - -```bash -vcluster connect [RELEASE_NAME] -n [RELEASE_NAMESPACE] -``` - -## Uninstall Helm Chart - -```bash -helm uninstall [RELEASE_NAME] -``` - -This removes all the Kubernetes components associated with the chart and deletes the release. - -See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation. - -### Why Virtual Kubernetes Clusters? - -- **Cluster Scoped Resources**: much more powerful than simple namespaces (virtual clusters allow users to use CRDs, namespaces, cluster roles etc.) -- **Ease of Use**: usable in any Kubernetes cluster and created in seconds either via a single command or [cluster-api](https://github.com/loft-sh/cluster-api-provider-vcluster) -- **Cost Efficient**: much cheaper and efficient than "real" clusters (single pod and shared resources just like for namespaces) -- **Lightweight**: built upon the ultra-fast k3s distribution with minimal overhead per virtual cluster (other distributions work as well) -- **Strict isolation**: complete separate Kubernetes control plane and access point for each vcluster while still being able to share certain services of the underlying host cluster -- **Cluster Wide Permissions**: allow users to install apps which require cluster-wide permissions while being limited to actually just one namespace within the host cluster -- **Great for Testing**: allow you to test different Kubernetes versions inside a single host cluster which may have a different version than the virtual clusters - -Learn more on [www.vcluster.com](https://vcluster.com). - -![vcluster Intro](https://github.com/loft-sh/vcluster/raw/main/docs/static/media/vcluster-comparison.png) - -Learn more in the [documentation](https://vcluster.com/docs/what-are-virtual-clusters). diff --git a/charts/eks/templates/NOTES.txt b/charts/eks/templates/NOTES.txt deleted file mode 100644 index 6cf960d62..000000000 --- a/charts/eks/templates/NOTES.txt +++ /dev/null @@ -1,10 +0,0 @@ -Thank you for installing vcluster. - -Your vcluster is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}. - -To connect to the vcluster, use vcluster CLI (https://www.vcluster.com/docs/getting-started/setup): - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} -- kubectl get ns - - -For more information, please take a look at the vcluster docs at https://www.vcluster.com/docs diff --git a/charts/eks/templates/_helpers.tpl b/charts/eks/templates/_helpers.tpl deleted file mode 100644 index 50f08d6f9..000000000 --- a/charts/eks/templates/_helpers.tpl +++ /dev/null @@ -1,196 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "vcluster.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Whether the ingressclasses syncer should be enabled -*/}} -{{- define "vcluster.syncIngressclassesEnabled" -}} -{{- if or - (.Values.sync.ingressclasses).enabled - (and - .Values.sync.ingresses.enabled - (not .Values.sync.ingressclasses)) -}} - {{- true -}} -{{- end -}} -{{- end -}} - -{{/* -Whether to create a cluster role or not -*/}} -{{- define "vcluster.createClusterRole" -}} -{{- if or - (not (empty (include "vcluster.serviceMapping.fromHost" . ))) - (not (empty (include "vcluster.plugin.clusterRoleExtraRules" . ))) - (not (empty (include "vcluster.generic.clusterRoleExtraRules" . ))) - .Values.rbac.clusterRole.create - .Values.sync.hoststorageclasses.enabled - (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") - (include "vcluster.syncIngressclassesEnabled" . ) - .Values.pro - .Values.sync.nodes.enabled - .Values.sync.persistentvolumes.enabled - .Values.sync.storageclasses.enabled - .Values.sync.priorityclasses.enabled - .Values.sync.volumesnapshots.enabled - .Values.proxy.metricsServer.nodes.enabled - .Values.multiNamespaceMode.enabled - .Values.coredns.plugin.enabled -}} -{{- true -}} -{{- end -}} -{{- end -}} - -{{- define "vcluster.clusterRoleName" -}} -{{- printf "vc-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "vcluster.clusterRoleNameMultinamespace" -}} -{{- printf "vc-mn-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Syncer flags for enabling/disabling controllers -Prints only the flags that modify the defaults: -- when default controller has enabled: false => `- "--sync=-controller` -- when non-default controller has enabled: true => `- "--sync=controller` -*/}} -{{- define "vcluster.syncer.syncArgs" -}} -{{- $defaultEnabled := list "services" "configmaps" "secrets" "endpoints" "pods" "events" "persistentvolumeclaims" "fake-nodes" "fake-persistentvolumes" -}} -{{- if and (hasKey .Values.sync.nodes "enableScheduler") .Values.sync.nodes.enableScheduler -}} - {{- $defaultEnabled = concat $defaultEnabled (list "csinodes" "csidrivers" "csistoragecapacities" ) -}} -{{- end -}} -{{- range $key, $val := .Values.sync }} -{{- if and (has $key $defaultEnabled) (not $val.enabled) }} -- --sync=-{{ $key }} -{{- else if and (not (has $key $defaultEnabled)) ($val.enabled)}} -{{- if eq $key "legacy-storageclasses" }} -- --sync=hoststorageclasses -{{- else }} -- --sync={{ $key }} -{{- end -}} -{{- end -}} -{{- end -}} -{{- if not (include "vcluster.syncIngressclassesEnabled" . ) }} -- --sync=-ingressclasses -{{- end -}} -{{- end -}} - -{{/* - Cluster role rules defined by plugins -*/}} -{{- define "vcluster.plugin.clusterRoleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.clusterRole }} -{{- if $container.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.clusterRoleExtraRules" -}} -{{- if .Values.sync.generic.clusterRole }} -{{- if .Values.sync.generic.clusterRole.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined on global level -*/}} -{{- define "vcluster.rbac.clusterRoleExtraRules" -}} -{{- if .Values.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - - -{{/* - Role rules defined on global level -*/}} -{{- define "vcluster.rbac.roleExtraRules" -}} -{{- if .Values.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined by plugins -*/}} -{{- define "vcluster.plugin.roleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.role }} -{{- if $container.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.roleExtraRules" -}} -{{- if .Values.sync.generic.role }} -{{- if .Values.sync.generic.role.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Virtual cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromVirtual" -}} -{{- range $key, $value := .Values.mapServices.fromVirtual }} -- '--map-virtual-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} - -{{/* - Host cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromHost" -}} -{{- range $key, $value := .Values.mapServices.fromHost }} -- '--map-host-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} diff --git a/charts/eks/templates/_kind.tpl b/charts/eks/templates/_kind.tpl deleted file mode 100644 index c560376e7..000000000 --- a/charts/eks/templates/_kind.tpl +++ /dev/null @@ -1,64 +0,0 @@ - -{{/* - deployment kind -*/}} -{{- define "vcluster.kind" -}} -{{ if and .Values.embeddedEtcd.enabled .Values.pro }}StatefulSet{{ else }}Deployment{{ end }} -{{- end -}} - -{{/* - service name for statefulset -*/}} -{{- define "vcluster.statefulset.serviceName" }} -{{- if .Values.embeddedEtcd.enabled }} -serviceName: {{ .Release.Name }}-headless -{{- end }} -{{- end -}} - -{{/* - volumeClaimTemplate -*/}} -{{- define "vcluster.statefulset.volumeClaimTemplate" }} -{{- if .Values.embeddedEtcd.enabled }} -{{- if .Values.autoDeletePersistentVolumeClaims }} -{{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} -persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete -{{- end }} -{{- end }} -{{- if (hasKey .Values "volumeClaimTemplates") }} -volumeClaimTemplates: -{{ toYaml .Values.volumeClaimTemplates | indent 4 }} -{{- else if .Values.syncer.storage.persistence }} -volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - {{- if .Values.syncer.storage.className }} - storageClassName: {{ .Values.syncer.storage.className }} - {{- end }} - resources: - requests: - storage: {{ .Values.syncer.storage.size }} -{{- end }} -{{- end }} -{{- end -}} - - -{{/* - deployment strategy -*/}} -{{- define "vcluster.deployment.strategy" }} -{{- if not .Values.embeddedEtcd.enabled }} -strategy: - rollingUpdate: - maxSurge: 1 - {{- if (eq (int .Values.syncer.replicas) 1) }} - maxUnavailable: 0 - {{- else }} - maxUnavailable: 1 - {{- end }} - type: RollingUpdate -{{- end }} -{{- end -}} diff --git a/charts/eks/templates/_plugin.tpl b/charts/eks/templates/_plugin.tpl deleted file mode 100644 index b1ecd2fa2..000000000 --- a/charts/eks/templates/_plugin.tpl +++ /dev/null @@ -1,188 +0,0 @@ -{{/* - Plugin config definition -*/}} -{{- define "vcluster.plugins.config" -}} -{{- $pluginFound := false -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} -{{- $pluginFound = true -}} -{{- end }} -{{- if $pluginFound }} -- name: PLUGIN_CONFIG - value: |- -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} - {{ $key }}: {{ toYaml $container.config | nindent 6 }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume mount definition -*/}} -{{- define "vcluster.plugins.volumeMounts" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- mountPath: /plugins - name: plugins -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume definition -*/}} -{{- define "vcluster.plugins.volumes" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- name: plugins - emptyDir: {} -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin init container definition -*/}} -{{- define "vcluster.plugins.initContainers" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if or $container.command $container.args }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- else }} - command: ["sh"] - args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] - {{- end }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - {{- if $container.volumeMounts }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- else }} - volumeMounts: - - mountPath: /plugins - name: plugins - {{- end }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* - Extra Syncer Args for the legacy Plugins -*/}} -{{- define "vcluster.legacyPlugins.args" -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{- continue }} -{{- end }} -{{- if not $container.optional }} -- --plugins={{ $key }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Sidecar container definition for the legacy syncer parts -*/}} -{{- define "vcluster.legacyPlugins.containers" -}} -{{- $counter := -1 -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{ continue }} -{{- end }} -{{- $counter = add1 $counter }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if $container.workingDir }} - workingDir: {{ $container.workingDir }} - {{- end }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- if $container.terminationMessagePath }} - terminationMessagePath: {{ $container.terminationMessagePath }} - {{- end }} - {{- if $container.terminationMessagePolicy }} - terminationMessagePolicy: {{ $container.terminationMessagePolicy }} - {{- end }} - env: - - name: VCLUSTER_PLUGIN_ADDRESS - value: "localhost:{{ add 14000 $counter }}" - - name: VCLUSTER_PLUGIN_NAME - value: "{{ $key }}" - {{- if $container.env }} -{{ toYaml $container.env | indent 4 }} - {{- end }} - envFrom: -{{ toYaml $container.envFrom | indent 4 }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - lifecycle: -{{ toYaml $container.lifecycle | indent 4 }} - livenessProbe: -{{ toYaml $container.livenessProbe | indent 4 }} - readinessProbe: -{{ toYaml $container.readinessProbe | indent 4 }} - startupProbe: -{{ toYaml $container.startupProbe | indent 4 }} - volumeDevices: -{{ toYaml $container.volumeDevices | indent 4 }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} - {{- end }} -{{- end }} - - diff --git a/charts/eks/templates/coredns.yaml b/charts/eks/templates/coredns.yaml deleted file mode 100644 index df520c17c..000000000 --- a/charts/eks/templates/coredns.yaml +++ /dev/null @@ -1,215 +0,0 @@ -{{- if not .Values.headless }} -{{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-coredns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations}} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4}} - {{- end}} -data: -{{- if .Values.coredns.manifests }} - coredns.yaml: |- -{{ .Values.coredns.manifests | indent 4 }} -{{- else }} - coredns.yaml: |- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: coredns - namespace: kube-system - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - rules: - - apiGroups: - - "" - resources: - - endpoints - - services - - pods - - namespaces - verbs: - - list - - watch - - apiGroups: - - discovery.k8s.io - resources: - - endpointslices - verbs: - - list - - watch - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - annotations: - rbac.authorization.kubernetes.io/autoupdate: "true" - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:coredns - subjects: - - kind: ServiceAccount - name: coredns - namespace: kube-system - --- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: -{{ include "vcluster.corefile" . | indent 6 }} - NodeHosts: "" - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: coredns - namespace: kube-system - labels: - k8s-app: kube-dns - kubernetes.io/name: "CoreDNS" - spec: - replicas: {{ .Values.coredns.replicas }} - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - selector: - matchLabels: - k8s-app: kube-dns - template: - metadata: - {{- if .Values.coredns.podAnnotations }} - annotations: -{{ toYaml .Values.coredns.podAnnotations | indent 12 }} - {{- end }} - labels: - k8s-app: kube-dns - {{- range $k, $v := .Values.coredns.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - priorityClassName: "system-cluster-critical" - serviceAccountName: coredns - nodeSelector: - kubernetes.io/os: linux - {{- if .Values.coredns.nodeSelector }} -{{ toYaml .Values.coredns.nodeSelector | indent 12 }} - {{- end }} - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: DoNotSchedule - labelSelector: - matchLabels: - k8s-app: kube-dns - {{- if .Values.isolation.enabled }} - securityContext: - seccompProfile: - type: RuntimeDefault - {{- end }} - containers: - - name: coredns - image: "{{ .Values.defaultImageRegistry }}{{ .Values.coredns.image }}" - imagePullPolicy: IfNotPresent - resources: -{{ toYaml .Values.coredns.resources | indent 14}} - args: [ "-conf", "/etc/coredns/Corefile" ] - volumeMounts: - - name: config-volume - mountPath: /etc/coredns - readOnly: true - - name: custom-config-volume - mountPath: /etc/coredns/custom - readOnly: true - securityContext: - runAsNonRoot: true - runAsUser: {{`{{.RUN_AS_USER}}`}} - runAsGroup: {{`{{.RUN_AS_GROUP}}`}} - allowPrivilegeEscalation: false - capabilities: - add: - - NET_BIND_SERVICE - drop: - - ALL - readOnlyRootFilesystem: true - livenessProbe: - httpGet: - path: /health - port: 8080 - scheme: HTTP - initialDelaySeconds: 60 - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /ready - port: 8181 - scheme: HTTP - initialDelaySeconds: 0 - periodSeconds: 2 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - dnsPolicy: Default - volumes: - - name: config-volume - configMap: - name: coredns - items: - - key: Corefile - path: Corefile - - key: NodeHosts - path: NodeHosts - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - selector: - k8s-app: kube-dns - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} -{{- end }} diff --git a/charts/eks/templates/etcd-service.yaml b/charts/eks/templates/etcd-service.yaml deleted file mode 100644 index 57c60a145..000000000 --- a/charts/eks/templates/etcd-service.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless ) (not .Values.etcd.disabled )) .Values.embeddedEtcd.migrateFromEtcd }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-etcd - namespace: {{ .Release.Namespace }} - labels: - app: vcluster-etcd - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: ClusterIP - ports: - - name: etcd - port: 2379 - targetPort: 2379 - protocol: TCP - - name: peer - port: 2380 - targetPort: 2380 - protocol: TCP - selector: - app: vcluster-etcd - release: {{ .Release.Name }} -{{- end }} diff --git a/charts/eks/templates/etcd-statefulset.yaml b/charts/eks/templates/etcd-statefulset.yaml deleted file mode 100644 index 2e3de57c0..000000000 --- a/charts/eks/templates/etcd-statefulset.yaml +++ /dev/null @@ -1,170 +0,0 @@ -{{- if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless ) (not .Values.etcd.disabled )) .Values.embeddedEtcd.migrateFromEtcd }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ .Release.Name }}-etcd - namespace: {{ .Release.Namespace }} - labels: - app: vcluster-etcd - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -{{- if .Values.etcd.labels }} -{{ toYaml .Values.etcd.labels | indent 4 }} -{{- end }} - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.annotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - serviceName: {{ .Release.Name }}-etcd-headless - {{- if .Values.autoDeletePersistentVolumeClaims }} - {{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} - persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete - {{- end }} - {{- end }} - replicas: {{ .Values.etcd.replicas }} - podManagementPolicy: Parallel - selector: - matchLabels: - app: vcluster-etcd - release: {{ .Release.Name }} - {{- if (hasKey .Values.etcd "volumeClaimTemplates") }} - volumeClaimTemplates: -{{ toYaml .Values.etcd.volumeClaimTemplates | indent 4 }} - {{- else if .Values.etcd.storage.persistence }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - {{- if .Values.etcd.storage.className }} - storageClassName: {{ .Values.etcd.storage.className }} - {{- end }} - resources: - requests: - storage: {{ .Values.etcd.storage.size }} - {{- end }} - template: - metadata: - {{- if .Values.etcd.podAnnotations }} - annotations: -{{ toYaml .Values.etcd.podAnnotations | indent 8 }} - {{- end }} - labels: - app: vcluster-etcd - release: {{ .Release.Name }} - {{- range $k, $v := .Values.etcd.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - terminationGracePeriodSeconds: 10 - {{- if .Values.etcd.affinity }} - affinity: -{{ toYaml .Values.etcd.affinity | indent 8 }} - {{- end }} - {{- if .Values.etcd.topologySpreadConstraints }} - topologySpreadConstraints: -{{ toYaml .Values.etcd.topologySpreadConstraints | indent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.etcd.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.etcd.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.etcd.tolerations | indent 8 }} - automountServiceAccountToken: false - volumes: - - name: certs - secret: - secretName: {{ .Release.Name }}-certs - {{- if .Values.etcd.volumes }} -{{ toYaml .Values.etcd.volumes | indent 8 }} - {{- end }} - {{- if not .Values.etcd.storage.persistence }} - - name: data - emptyDir: {} - {{- end }} - {{- if .Values.etcd.priorityClassName }} - priorityClassName: {{ .Values.etcd.priorityClassName }} - {{- end }} - {{- if .Values.etcd.fsGroup }} - securityContext: - fsGroup: {{ .Values.etcd.fsGroup }} - {{- end }} - containers: - - name: etcd - image: "{{ .Values.defaultImageRegistry }}{{ .Values.etcd.image }}" - command: - - etcd - - '--cert-file=/run/config/pki/etcd-server.crt' - - '--client-cert-auth=true' - - '--data-dir=/var/lib/etcd' - - '--advertise-client-urls=https://$(NAME).{{ .Release.Name }}-etcd-headless.{{ .Release.Namespace }}:2379' - - '--initial-advertise-peer-urls=https://$(NAME).{{ .Release.Name }}-etcd-headless.{{ .Release.Namespace }}:2380' - {{- $releaseName := .Release.Name -}} - {{- $releaseNamespace := .Release.Namespace }} - - '--initial-cluster={{ range $index := untilStep 0 (int .Values.etcd.replicas) 1 }}{{ if (ne (int $index) 0) }},{{ end }}{{ $releaseName }}-etcd-{{ $index }}=https://{{ $releaseName }}-etcd-{{ $index }}.{{ $releaseName }}-etcd-headless.{{ $releaseNamespace }}:2380{{ end }}' - - '--initial-cluster-token={{ .Release.Name }}' - - '--initial-cluster-state=new' - - '--listen-client-urls=https://0.0.0.0:2379' - - '--listen-metrics-urls=http://0.0.0.0:2381' - - '--listen-peer-urls=https://0.0.0.0:2380' - - '--key-file=/run/config/pki/etcd-server.key' - - '--name=$(NAME)' - - '--peer-cert-file=/run/config/pki/etcd-peer.crt' - - '--peer-client-cert-auth=true' - - '--peer-key-file=/run/config/pki/etcd-peer.key' - - '--peer-trusted-ca-file=/run/config/pki/etcd-ca.crt' - - '--snapshot-count=10000' - - '--trusted-ca-file=/run/config/pki/etcd-ca.crt' - {{- range $f := .Values.etcd.extraArgs }} - - {{ $f | quote }} - {{- end }} - securityContext: -{{ toYaml .Values.etcd.securityContext | indent 10 }} - {{- if .Values.etcd.imagePullPolicy }} - imagePullPolicy: {{ .Values.etcd.imagePullPolicy }} - {{- end }} - env: - - name: NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if .Values.etcd.env }} -{{ toYaml .Values.etcd.env | indent 10 }} - {{- end }} - volumeMounts: - - name: data - mountPath: /var/lib/etcd - - mountPath: /run/config/pki - name: certs - readOnly: true - {{- if .Values.etcd.volumeMounts }} -{{ toYaml .Values.etcd.volumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.etcd.resources | indent 10 }} - livenessProbe: - httpGet: - path: /health - port: 2381 - scheme: HTTP - initialDelaySeconds: 10 - timeoutSeconds: 15 - periodSeconds: 10 - successThreshold: 1 - failureThreshold: 8 - startupProbe: - httpGet: - path: /health - port: 2381 - scheme: HTTP - initialDelaySeconds: 10 - timeoutSeconds: 15 - periodSeconds: 10 - successThreshold: 1 - failureThreshold: 24 -{{- end }} diff --git a/charts/eks/templates/ingress.yaml b/charts/eks/templates/ingress.yaml deleted file mode 100644 index 26191ffb6..000000000 --- a/charts/eks/templates/ingress.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - {{- $annotations := merge .Values.ingress.annotations .Values.globalAnnotations }} - {{- if .Values.ingress.tls }} - {{- $annotations = omit $annotations "nginx.ingress.kubernetes.io/ssl-passthrough" }} - {{- end }} - {{- if $annotations }} - annotations: - {{- toYaml $annotations | nindent 4 }} - {{- end }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - {{- if .Values.ingress.ingressClassName }} - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} - {{- end }} - rules: - - host: {{ .Values.ingress.host | quote }} - http: - paths: - - backend: - service: - name: {{ .Release.Name }} - port: - name: https - path: / - pathType: {{ .Values.ingress.pathType }} - {{- with .Values.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end -}} -{{- end }} diff --git a/charts/eks/templates/init-configmap.yaml b/charts/eks/templates/init-configmap.yaml deleted file mode 100644 index cc461ae6c..000000000 --- a/charts/eks/templates/init-configmap.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.init.manifests }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-init-manifests - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations}} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: - manifests: |- - {{ .Values.init.manifests | nindent 4 | trim }} - {{ tpl .Values.init.manifestsTemplate $ | nindent 4 | trim }} - {{- if .Values.init.helm }} - charts: |- - {{- range .Values.init.helm }} - {{- /* only render this chart entry if either of chart or bundle is defined */}} - {{- if .chart }} - - name: {{ .chart.name }} - repo: {{ .chart.repo }} - version: {{ .chart.version }} - {{- if .chart.username }} - username: {{ .chart.username }} - {{- end }} - {{- if .chart.password }} - password: {{ .chart.password }} - {{- end }} - {{- if .insecure }} - insecure: true - {{- end}} - {{- end }} - {{- if .bundle }} - bundle: {{ .bundle }} - {{- end }} - {{- if or .chart .bundle }} - {{- if or .values .valuesTemplate }} - values: |- - {{ (.values | default "") | nindent 8 | trim }} - {{ tpl (.valuesTemplate | default "") $ | nindent 8 | trim }} - {{- end}} - {{- if .release }} - timeout: {{ .timeout | default "120s" | quote }} - releaseName: {{ .release.name }} - releaseNamespace: {{ .release.namespace }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/eks/templates/integrated-coredns.yaml b/charts/eks/templates/integrated-coredns.yaml deleted file mode 100644 index 16907cba0..000000000 --- a/charts/eks/templates/integrated-coredns.yaml +++ /dev/null @@ -1,64 +0,0 @@ -{{- if .Values.pro }} -{{- if .Values.coredns.integrated }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-dns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{ include "vcluster.corefile" . | indent 2 }} - coredns.yaml: |- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: - NodeHosts: "" - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} diff --git a/charts/eks/templates/limitrange.yaml b/charts/eks/templates/limitrange.yaml deleted file mode 100644 index ed219d063..000000000 --- a/charts/eks/templates/limitrange.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.limitRange.enabled }} -apiVersion: v1 -kind: LimitRange -metadata: - name: {{ .Release.Name }}-limit-range - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - limits: - - default: - {{- range $key, $val := .Values.isolation.limitRange.default }} - {{ $key }}: {{ $val | quote }} - {{- end }} - defaultRequest: - {{- range $key, $val := .Values.isolation.limitRange.defaultRequest }} - {{ $key }}: {{ $val | quote }} - {{- end }} - type: Container -{{- end }} diff --git a/charts/eks/templates/networkpolicy.yaml b/charts/eks/templates/networkpolicy.yaml deleted file mode 100644 index 81886898b..000000000 --- a/charts/eks/templates/networkpolicy.yaml +++ /dev/null @@ -1,78 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-workloads - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - egress: - # Allows outgoing connections to the vcluster control plane - - ports: - - port: 443 - - port: 8443 - to: - - podSelector: - matchLabels: - release: {{ .Release.Name }} - # Allows outgoing connections to DNS server - - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP - # Allows outgoing connections to the internet or - # other vcluster workloads - - to: - - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - - ipBlock: - cidr: {{ .Values.isolation.networkPolicy.outgoingConnections.ipBlock.cidr }} - except: - {{- range .Values.isolation.networkPolicy.outgoingConnections.ipBlock.except }} - - {{ . }} - {{- end }} - policyTypes: - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-control-plane - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - release: {{ .Release.Name }} - egress: - # Allows outgoing connections to all pods with - # port 443, 8443 or 6443. This is needed for host Kubernetes - # access - - ports: - - port: 443 - - port: 8443 - - port: 6443 - # Allows outgoing connections to all vcluster workloads - # or kube system dns server - - to: - - podSelector: {} - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: 'kube-system' - podSelector: - matchLabels: - k8s-app: kube-dns - policyTypes: - - Egress -{{- end }} \ No newline at end of file diff --git a/charts/eks/templates/rbac/clusterrole.yaml b/charts/eks/templates/rbac/clusterrole.yaml deleted file mode 100644 index 984267cae..000000000 --- a/charts/eks/templates/rbac/clusterrole.yaml +++ /dev/null @@ -1,103 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: -{{- if .Values.pro }} - - apiGroups: ["cluster.loft.sh", "storage.loft.sh"] - resources: ["features", "virtualclusters"] - verbs: ["get", "list", "watch"] -{{- end }} - {{- if or .Values.pro .Values.sync.nodes.enabled }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["get", "watch", "list"] - - apiGroups: [""] - resources: [ "pods", "nodes/metrics", "nodes/stats"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.coredns.plugin.enabled }} - - apiGroups: [""] - resources: [ "pods"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled (or (not .Values.isolation.enabled) (and .Values.isolation.nodeProxyPermission.enabled .Values.isolation.enabled)) }} - - apiGroups: [""] - resources: ["nodes/proxy"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled .Values.sync.nodes.syncNodeChanges }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["update", "patch"] - {{- end }} - {{- if .Values.sync.persistentvolumes.enabled }} - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if .Values.sync.nodes.enableScheduler }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses","csinodes","csidrivers","csistoragecapacities"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if (include "vcluster.syncIngressclassesEnabled" . ) }} - - apiGroups: ["networking.k8s.io"] - resources: ["ingressclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.storageclasses.enabled }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if or .Values.sync.hoststorageclasses.enabled (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") .Values.rbac.clusterRole.create }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.priorityclasses.enabled }} - - apiGroups: ["scheduling.k8s.io"] - resources: ["priorityclasses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if (not (empty (include "vcluster.serviceMapping.fromHost" . ))) }} - - apiGroups: [""] - resources: ["services", "endpoints"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.proxy.metricsServer.nodes.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["nodes"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.clusterRoleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/eks/templates/rbac/rolebinding.yaml b/charts/eks/templates/rbac/rolebinding.yaml deleted file mode 100644 index 676e5002a..000000000 --- a/charts/eks/templates/rbac/rolebinding.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRoleBinding -{{- else -}} -kind: RoleBinding -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -{{- end }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: -{{- if .Values.multiNamespaceMode.enabled }} - kind: ClusterRole - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - kind: Role - name: {{ .Release.Name }} -{{- end }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/eks/templates/resourcequota.yaml b/charts/eks/templates/resourcequota.yaml deleted file mode 100644 index b19d89fff..000000000 --- a/charts/eks/templates/resourcequota.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.resourceQuota.enabled }} -apiVersion: v1 -kind: ResourceQuota -metadata: - name: {{ .Release.Name }}-quota - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - hard: - {{- range $key, $val := .Values.isolation.resourceQuota.quota }} - {{ $key }}: {{ $val | quote }} - {{- end }} - - {{- if .Values.isolation.resourceQuota.scopeSelector.matchExpressions }} - scopeSelector: - matchExpressions: - {{- toYaml .Values.isolation.resourceQuota.scopeSelector.matchExpressions | nindent 4 }} - {{- end}} - - {{- if .Values.isolation.resourceQuota.scopes }} - scopes: - {{- toYaml .Values.isolation.resourceQuota.scopes | nindent 4 }} - {{- end}} - -{{- end }} diff --git a/charts/eks/templates/service.yaml b/charts/eks/templates/service.yaml deleted file mode 100644 index b575a9fe1..000000000 --- a/charts/eks/templates/service.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: {{ if eq .Values.service.type "LoadBalancer" -}}ClusterIP{{- else }} {{- .Values.service.type }} {{- end }} - ports: - - name: https - port: 443 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.httpsNodePort }} - protocol: TCP - - name: kubelet - port: 10250 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.kubeletNodePort }} - protocol: TCP - {{- if .Values.service.externalIPs }} - externalIPs: - {{- range $f := .Values.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- if eq .Values.service.type "NodePort" }} - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} ---- -{{ if eq .Values.service.type "LoadBalancer" }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-lb - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.service.loadBalancerAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: LoadBalancer - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} - {{- if .Values.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.service.loadBalancerIP }} - {{- end }} - {{- if .Values.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.service.loadBalancerClass }} - {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: - {{- range $f := .Values.service.loadBalancerSourceRanges }} - - "{{ $f }}" - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/eks/templates/serviceaccount.yaml b/charts/eks/templates/serviceaccount.yaml deleted file mode 100644 index 504998037..000000000 --- a/charts/eks/templates/serviceaccount.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} -{{- end }} diff --git a/charts/eks/templates/syncer.yaml b/charts/eks/templates/syncer.yaml deleted file mode 100644 index b82ac99eb..000000000 --- a/charts/eks/templates/syncer.yaml +++ /dev/null @@ -1,447 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: apps/v1 -kind: {{ include "vcluster.kind" . }} -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -{{- if .Values.syncer.labels }} -{{ toYaml .Values.syncer.labels | indent 4 }} -{{- end }} - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.annotations }} - {{- if $annotations}} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - {{- include "vcluster.deployment.strategy" . | indent 2 }} - {{- include "vcluster.statefulset.serviceName" . | indent 2 }} - {{- include "vcluster.statefulset.volumeClaimTemplate" . | indent 2 }} - replicas: {{ .Values.syncer.replicas }} - selector: - matchLabels: - app: vcluster - release: {{ .Release.Name }} - template: - metadata: - {{- if .Values.syncer.podAnnotations }} - annotations: -{{ toYaml .Values.syncer.podAnnotations | indent 8 }} - {{- end }} - labels: - app: vcluster - release: {{ .Release.Name }} - {{- range $k, $v := .Values.syncer.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - terminationGracePeriodSeconds: 10 - {{- if .Values.syncer.affinity }} - affinity: -{{ toYaml .Values.syncer.affinity | indent 8 }} - {{- else if (gt (int .Values.syncer.replicas) 1) }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - # if possible avoid scheduling more than one pod on one node - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: "kubernetes.io/hostname" - # if possible avoid scheduling pod onto node that is in the same zone as one or more vcluster pods are running - - weight: 50 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: topology.kubernetes.io/zone - {{- end }} - {{- if .Values.syncer.topologySpreadConstraints }} - topologySpreadConstraints: -{{ toYaml .Values.syncer.topologySpreadConstraints | indent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.syncer.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.syncer.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.syncer.tolerations | indent 8 }} - {{- if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} - {{- else }} - serviceAccountName: vc-{{ .Release.Name }} - {{- end }} - {{- if .Values.syncer.podSecurityContext }} - securityContext: -{{ toYaml .Values.syncer.podSecurityContext | indent 8 }} - {{- end }} - volumes: - {{- include "vcluster.plugins.volumes" . | indent 8 }} - - name: helm-cache - emptyDir: {} - - name: tmp - emptyDir: {} - - name: certs - emptyDir: {} - {{- if .Values.volumes }} -{{ toYaml .Values.volumes | indent 8 }} - {{- end }} - - name: binaries - emptyDir: {} - {{- if .Values.syncer.volumes }} -{{ toYaml .Values.syncer.volumes | indent 8 }} - {{- end }} - {{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} - - name: coredns - configMap: - name: {{ .Release.Name }}-coredns - {{- else if .Values.coredns.integrated }} - - name: coredns - configMap: - name: {{ .Release.Name }}-dns - {{- end }} - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - {{- if .Values.syncer.priorityClassName }} - priorityClassName: {{ .Values.syncer.priorityClassName }} - {{- end }} - initContainers: - {{- include "vcluster.plugins.initContainers" . | indent 6 }} - # this is needed because the k8s containers are distroless and thus we don't have any - # way of copying the binaries otherwise - - name: vcluster-copy - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /bin/sh - args: - - -c - - "cp /vcluster /binaries/vcluster" - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- if not .Values.controller.disabled }} - - name: kube-controller-manager - image: "{{ .Values.defaultImageRegistry }}{{ .Values.controller.image }}" - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /binaries/vcluster - args: - - cp - - /usr/local/bin/kube-controller-manager - - /binaries/kube-controller-manager - {{- if .Values.controller.imagePullPolicy }} - imagePullPolicy: {{ .Values.controller.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} - {{- if not .Values.api.disabled }} - - name: kube-apiserver - image: "{{ .Values.defaultImageRegistry }}{{ .Values.api.image }}" - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /binaries/vcluster - args: - - cp - - /usr/local/bin/kube-apiserver - - /binaries/kube-apiserver - {{- if .Values.api.imagePullPolicy }} - imagePullPolicy: {{ .Values.api.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} - containers: - - name: syncer - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - {{- if .Values.syncer.workingDir }} - workingDir: {{ .Values.syncer.workingDir }} - {{- end }} - {{- if .Values.syncer.command }} - command: - {{- range $f := .Values.syncer.command }} - - {{ $f | quote }} - {{- end }} - {{- end }} - args: - - --name={{ .Release.Name }} - - --request-header-ca-cert=/pki/front-proxy-ca.crt - - --client-ca-cert=/pki/ca.crt - - --server-ca-cert=/pki/ca.crt - - --server-ca-key=/pki/ca.key - - --kube-config=/pki/admin.conf - - --service-account=vc-workload-{{ .Release.Name }} - {{- if .Values.embeddedEtcd.migrateFromEtcd }} - - --migrate-from=https://{{ .Release.Name }}-etcd:2379 - {{- end }} - {{- include "vcluster.legacyPlugins.args" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromHost" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromVirtual" . | indent 10 }} - {{- if .Values.defaultImageRegistry }} - - --default-image-registry={{ .Values.defaultImageRegistry }} - {{- end }} - {{- if .Values.syncer.kubeConfigContextName }} - - --kube-config-context-name={{ .Values.syncer.kubeConfigContextName }} - {{- end }} - {{- if .Values.pro }} - {{- if .Values.proLicenseSecret }} - - --pro-license-secret={{ .Values.proLicenseSecret }} - {{- end }} - {{- if .Values.embeddedEtcd.enabled }} - - --etcd-embedded - - --etcd-replicas={{ .Values.syncer.replicas }} - {{- end }} - {{- end }} - {{- if (gt (int .Values.syncer.replicas) 1) }} - - --leader-elect=true - {{- else }} - - --leader-elect=false - {{- end }} - {{- if .Values.ingress.enabled }} - - --tls-san={{ .Values.ingress.host }} - {{- end }} - {{- include "vcluster.syncer.syncArgs" . | indent 10 -}} - {{- if .Values.sync.nodes.syncAllNodes }} - - --sync-all-nodes - {{- end }} - {{- if .Values.isolation.enabled }} - - --enforce-pod-security-standard={{ .Values.isolation.podSecurityStandard }} - {{- end}} - {{- if .Values.sync.nodes.nodeSelector }} - - --node-selector={{ .Values.sync.nodes.nodeSelector }} - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - --multi-namespace-mode=true - {{- end }} - {{- if .Values.sync.configmaps.all }} - - --sync-all-configmaps=true - {{- end }} - {{- if .Values.sync.secrets.all }} - - --sync-all-secrets=true - {{- end }} - {{- if not .Values.sync.nodes.fakeKubeletIPs }} - - --fake-kubelet-ips=false - {{- end }} - {{- if or .Values.proxy.metricsServer.nodes.enabled .Values.proxy.metricsServer.pods.enabled }} - - --proxy-metrics-server=true - {{- end }} - {{- if .Values.coredns.integrated }} - - --integrated-coredns=true - {{- end }} - {{- if .Values.centralAdmission.validatingWebhooks }} - {{- range .Values.centralAdmission.validatingWebhooks }} - - --enforce-validating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- if .Values.centralAdmission.mutatingWebhooks }} - {{- range .Values.centralAdmission.mutatingWebhooks }} - - --enforce-mutating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - - --use-coredns-plugin=true - {{- end }} - {{- range $f := .Values.syncer.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- if .Values.syncer.livenessProbe }} - {{- if .Values.syncer.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: /healthz - port: 8443 - scheme: HTTPS - failureThreshold: 10 - initialDelaySeconds: 60 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.readinessProbe }} - {{- if .Values.syncer.readinessProbe.enabled }} - startupProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 300 - periodSeconds: 6 - readinessProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 30 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.syncer.securityContext | indent 10 }} - env: - {{- include "vcluster.plugins.config" . | indent 10 }} - - name: VCLUSTER_DISTRO - value: eks - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if eq (.Values.syncer.replicas | toString | atoi) 1 }} - - name: VCLUSTER_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - {{- end }} - {{- if .Values.syncer.env }} -{{ toYaml .Values.syncer.env | indent 10 }} - {{- end }} - {{- if .Values.sync.generic.config }} - - name: CONFIG - value: |- - {{- .Values.sync.generic.config | nindent 14 }} - {{- end }} - - name: VCLUSTER_TELEMETRY_CONFIG - value: {{ .Values.telemetry | toJson | quote }} - {{- if not .Values.api.disabled }} - - name: APISERVER_COMMAND - value: |- - command: - - /binaries/kube-apiserver - - '--advertise-address=127.0.0.1' - - '--bind-address=127.0.0.1' - - '--allow-privileged=true' - - '--authorization-mode=RBAC' - - '--client-ca-file=/pki/ca.crt' - - '--enable-bootstrap-token-auth=true' - - '--etcd-cafile=/pki/etcd/ca.crt' - - '--etcd-certfile=/pki/apiserver-etcd-client.crt' - - '--etcd-keyfile=/pki/apiserver-etcd-client.key' - {{- if .Values.embeddedEtcd.enabled }} - - '--etcd-servers=https://127.0.0.1:2379' - {{- else }} - - '--etcd-servers=https://{{ .Release.Name }}-etcd:2379' - {{- end }} - - '--proxy-client-cert-file=/pki/front-proxy-client.crt' - - '--proxy-client-key-file=/pki/front-proxy-client.key' - - '--requestheader-allowed-names=front-proxy-client' - - '--requestheader-client-ca-file=/pki/front-proxy-ca.crt' - - '--requestheader-extra-headers-prefix=X-Remote-Extra-' - - '--requestheader-group-headers=X-Remote-Group' - - '--requestheader-username-headers=X-Remote-User' - - '--secure-port=6443' - - '--service-account-issuer=https://kubernetes.default.svc.cluster.local' - - '--service-account-key-file=/pki/sa.pub' - - '--service-account-signing-key-file=/pki/sa.key' - - '--tls-cert-file=/pki/apiserver.crt' - - '--tls-private-key-file=/pki/apiserver.key' - - '--watch-cache=false' - - '--endpoint-reconciler-type=none' - {{- range $f := .Values.api.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- end }} - {{- if not .Values.controller.disabled }} - - name: CONTROLLER_COMMAND - value: |- - command: - - /binaries/kube-controller-manager - - '--authentication-kubeconfig=/pki/controller-manager.conf' - - '--authorization-kubeconfig=/pki/controller-manager.conf' - - '--bind-address=127.0.0.1' - - '--client-ca-file=/pki/ca.crt' - - '--cluster-name=kubernetes' - - '--cluster-signing-cert-file=/pki/ca.crt' - - '--cluster-signing-key-file=/pki/ca.key' - - '--controllers=*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' - - '--node-monitor-grace-period=1h' - - '--node-monitor-period=1h' - - '--horizontal-pod-autoscaler-sync-period=60s' - - '--kubeconfig=/pki/controller-manager.conf' - {{- if (gt (int .Values.syncer.replicas) 1) }} - - '--leader-elect=true' - {{- else }} - - '--leader-elect=false' - {{- end }} - - '--node-monitor-grace-period=180s' - - '--node-monitor-period=30s' - - '--pvclaimbinder-sync-period=60s' - - '--requestheader-client-ca-file=/pki/front-proxy-ca.crt' - - '--root-ca-file=/pki/ca.crt' - - '--service-account-private-key-file=/pki/sa.key' - - '--use-service-account-credentials=true' - {{- range $f := .Values.controller.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- end }} - volumeMounts: - {{- include "vcluster.plugins.volumeMounts" . | indent 10 }} - {{- if eq ( include "vcluster.kind" . ) "StatefulSet" }} - - name: data - mountPath: /data - {{- end }} - - name: helm-cache - mountPath: /.cache/helm - - name: tmp - mountPath: /tmp - - mountPath: /pki - name: certs - - mountPath: /binaries - name: binaries - {{- if .Values.syncer.volumeMounts }} -{{ toYaml .Values.syncer.volumeMounts | indent 10 }} - {{- end }} - {{- if .Values.syncer.extraVolumeMounts }} -{{ toYaml .Values.syncer.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} -{{- include "vcluster.legacyPlugins.containers" . | indent 6 }} -{{- end }} diff --git a/charts/eks/templates/workloadserviceaccount.yaml b/charts/eks/templates/workloadserviceaccount.yaml deleted file mode 100644 index 8d05fc1e8..000000000 --- a/charts/eks/templates/workloadserviceaccount.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-workload-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.workloadServiceAccount.annotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} diff --git a/charts/eks/tests/README.md b/charts/eks/tests/README.md deleted file mode 100644 index 228e10d6e..000000000 --- a/charts/eks/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Add [unittest plugin](https://github.com/helm-unittest/helm-unittest) via: -``` -helm plugin install https://github.com/helm-unittest/helm-unittest.git -``` - -Run tests via: -``` -helm unittest charts/eks -d -``` diff --git a/charts/eks/tests/clusterrole_test.yaml b/charts/eks/tests/clusterrole_test.yaml deleted file mode 100644 index 3385c786c..000000000 --- a/charts/eks/tests/clusterrole_test.yaml +++ /dev/null @@ -1,41 +0,0 @@ -suite: ClusterRole -templates: - - rbac/clusterrole.yaml - -tests: - - it: should create clusterrole - set: - rbac: - clusterRole: - create: true - asserts: - - hasDocuments: - count: 1 - - it: should not create clusterrole - set: - rbac: - clusterRole: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - clusterRole: - create: true - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/eks/tests/etcd_test.yaml b/charts/eks/tests/etcd_test.yaml deleted file mode 100644 index 931ce5d34..000000000 --- a/charts/eks/tests/etcd_test.yaml +++ /dev/null @@ -1,26 +0,0 @@ -suite: Etcd -templates: - - etcd-statefulset.yaml - - etcd-service.yaml - - etcd-statefulset-service.yaml - -tests: - - it: should have etcd when migratefrometcd is true - set: - pro: true - embeddedEtcd: - enabled: true - migrateFromEtcd: true - asserts: - - hasDocuments: - count: 1 - - it: shouldn't have etcd when migratefrometcd is false - set: - pro: true - embeddedEtcd: - enabled: true - migrateFromEtcd: false - asserts: - - hasDocuments: - count: 0 - diff --git a/charts/eks/tests/legacy-plugins_test.yaml b/charts/eks/tests/legacy-plugins_test.yaml deleted file mode 100644 index 6977c7562..000000000 --- a/charts/eks/tests/legacy-plugins_test.yaml +++ /dev/null @@ -1,77 +0,0 @@ -suite: Legacy Plugins -templates: - - syncer.yaml - -tests: - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - bootstrap-with-deployment2: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment2" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[2].name - value: bootstrap-with-deployment2 - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[2].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - equal: - path: spec.template.spec.containers[2].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14001 - - - it: should check no legacy plugin rendering - asserts: - - hasDocuments: - count: 1 - - lengthEqual: - path: spec.template.spec.containers - count: 1 diff --git a/charts/eks/tests/plugins_test.yaml b/charts/eks/tests/plugins_test.yaml deleted file mode 100644 index 2ce3149dc..000000000 --- a/charts/eks/tests/plugins_test.yaml +++ /dev/null @@ -1,100 +0,0 @@ -suite: Plugins -templates: - - syncer.yaml - -tests: - - it: should check plugin config rendering - set: - plugin: - plugin1: - version: v2 - config: - myConfig: true - plugin2: - version: v2 - image: test - plugin3: - version: v2 - image: test123 - config: - myOtherConfig: - - test123 - - test456 - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.containers[0].env[0].name - value: PLUGIN_CONFIG - - equal: - path: spec.template.spec.containers[0].env[0].value - value: |- - plugin1: - myConfig: true - plugin3: - myOtherConfig: - - test123 - - test456 - - - it: should check plugin rendering - set: - plugin: - bootstrap-with-deployment: - version: v2 - image: test - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.volumes[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins diff --git a/charts/eks/tests/role_test.yaml b/charts/eks/tests/role_test.yaml deleted file mode 100644 index 5eeedba74..000000000 --- a/charts/eks/tests/role_test.yaml +++ /dev/null @@ -1,32 +0,0 @@ -suite: Role -templates: - - rbac/role.yaml - -tests: - - it: should not create role - set: - rbac: - role: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - role: - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/eks/tests/syncer_test.yaml b/charts/eks/tests/syncer_test.yaml deleted file mode 100644 index 7713b2e10..000000000 --- a/charts/eks/tests/syncer_test.yaml +++ /dev/null @@ -1,52 +0,0 @@ -suite: Syncer -templates: - - syncer.yaml - -tests: - - it: should pass pro license secret as a flag - set: - pro: true - proLicenseSecret: "my-test-secret" - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--pro-license-secret=my-test-secret" - count: 1 - - it: should be a statefulset when embeddedEtcd is enabled - set: - pro: true - embeddedEtcd: - enabled: true - asserts: - - hasDocuments: - count: 1 - - isKind: - of: StatefulSet - - exists: - path: spec.serviceName - - exists: - path: spec.volumeClaimTemplates - - notExists: - path: spec.strategy - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /data - - - it: should be a deployment when embeddedEtcd is disabled - set: - pro: true - embeddedEtcd: - enabled: false - asserts: - - hasDocuments: - count: 1 - - isKind: - of: Deployment - - notExists: - path: spec.serviceName - - notExists: - path: spec.volumeClaimTemplates - - exists: - path: spec.strategy diff --git a/charts/eks/values.yaml b/charts/eks/values.yaml deleted file mode 100644 index a30f55b29..000000000 --- a/charts/eks/values.yaml +++ /dev/null @@ -1,505 +0,0 @@ -# DefaultImageRegistry will be prepended to all deployed vcluster images, such as the vcluster pod, coredns etc.. Deployed -# images within the vcluster will not be rewritten. -defaultImageRegistry: "" - -# Global annotations to add to all objects -globalAnnotations: {} - -# If vCluster.Pro is enabled -pro: false - -# Defines where vCluster should search for a license secret. If you are using the vCluster.Pro control-plane, -# this is optional. vCluster by default will lookout for a secret called vc-VCLUSTER_NAME-license and if found use that. -# You can also use a secret within another namespace by using the format NAMESPACE/NAME. If the secret is in another -# namespace, please enable clusterRole.create and define an extra clusterRole.extraRules to allow vCluster to retrieve -# secrets within the cluster. -proLicenseSecret: "" - -# Embedded etcd settings -embeddedEtcd: - # If embedded etcd should be enabled, this is a PRO only feature - enabled: false - # To use if embeddedEtcd is enabled and you want to migrate the data - # from the external etcd - migrateFromEtcd: false - -# Extra Annotations for the stateful set -annotations: {} -podAnnotations: {} - -headless: false - -monitoring: - serviceMonitor: - enabled: false - -# Plugins that should get loaded. Usually you want to apply those via 'vcluster create ... -f https://.../plugin.yaml' -plugin: {} -# Manually configure a plugin called test -# test: -# image: ... -# env: ... -# rbac: -# clusterRole: -# extraRules: ... -# role: -# extraRules: ... - -# Resource syncers that should be enabled/disabled. -# Enabling syncers will impact RBAC Role and ClusterRole permissions. -# To disable a syncer set "enabled: false". -# See docs for details - https://www.vcluster.com/docs/architecture/synced-resources -sync: - services: - enabled: true - configmaps: - enabled: true - all: false - secrets: - enabled: true - all: false - endpoints: - enabled: true - pods: - enabled: true - ephemeralContainers: false - status: false - events: - enabled: true - persistentvolumeclaims: - enabled: true - ingresses: - enabled: false - ingressclasses: {} - # By default IngressClasses sync is enabled when the Ingress sync is enabled - # but it can be explicitly disabled by setting: - # enabled: false - fake-nodes: - enabled: true # will be ignored if nodes.enabled = true - fake-persistentvolumes: - enabled: true # will be ignored if persistentvolumes.enabled = true - nodes: - fakeKubeletIPs: true - enabled: false - # If nodes sync is enabled, and syncAllNodes = true, the virtual cluster - # will sync all nodes instead of only the ones where some pods are running. - syncAllNodes: false - # nodeSelector is used to limit which nodes get synced to the vcluster, - # and which nodes are used to run vcluster pods. - # A valid string representation of a label selector must be used. - nodeSelector: "" - # syncNodeChanges allows vcluster user edits of the nodes to be synced down to the host nodes. - # Write permissions on node resource will be given to the vcluster. - syncNodeChanges: false - persistentvolumes: - enabled: false - storageclasses: - enabled: false - # formerly named - "legacy-storageclasses" - hoststorageclasses: - enabled: false - priorityclasses: - enabled: false - networkpolicies: - enabled: false - volumesnapshots: - enabled: false - poddisruptionbudgets: - enabled: false - serviceaccounts: - enabled: false - # generic CRD configuration - generic: - config: |- - --- - -# If enabled, will fallback to host dns for resolving domains. This -# is useful if using istio or dapr in the host cluster and sidecar -# containers cannot connect to the central instance. Its also useful -# if you want to access host cluster services from within the vcluster. -fallbackHostDns: false - -# Map Services between host and virtual cluster -mapServices: - # Services that should get mapped from the - # virtual cluster to the host cluster. - # vcluster will make sure to sync the service - # ip to the host cluster automatically as soon - # as the service exists. - # For example: - # fromVirtual: - # - from: my-namespace/name - # to: host-service - fromVirtual: [] - # Same as from virtual, but instead sync services - # from the host cluster into the virtual cluster. - # If the namespace does not exist, vcluster will - # also create the namespace for the service. - fromHost: [] - -proxy: - metricsServer: - nodes: - enabled: false - pods: - enabled: false - - -# Syncer configuration -syncer: - # Image to use for the syncer - # image: ghcr.io/loft-sh/vcluster - imagePullPolicy: "" - extraArgs: [] - volumeMounts: - - mountPath: /manifests/coredns - name: coredns - readOnly: true - extraVolumeMounts: [] - env: [] - livenessProbe: - enabled: true - readinessProbe: - enabled: true - resources: - limits: - ephemeral-storage: 8Gi - memory: 2Gi - requests: - ephemeral-storage: 200Mi - cpu: 10m - memory: 256Mi - # Extra volumes - volumes: [] - # The amount of replicas to run the deployment with - replicas: 1 - # NodeSelector used to schedule the syncer - nodeSelector: {} - # Affinity to apply to the syncer deployment - affinity: {} - # Tolerations to apply to the syncer deployment - tolerations: [] - # Extra Labels for the syncer deployment - labels: {} - # Extra Annotations for the syncer deployment - annotations: {} - podAnnotations: {} - podLabels: {} - priorityClassName: "" - kubeConfigContextName: "my-vcluster" - # Security context configuration - securityContext: - allowPrivilegeEscalation: false - podSecurityContext: - runAsUser: 0 - runAsGroup: 0 - serviceAnnotations: {} - # Storage settings for the vcluster - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - -# Etcd settings -etcd: - image: public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.9-eks-1-28-6 - imagePullPolicy: "" - # The amount of replicas to run - replicas: 1 - # NodeSelector used - nodeSelector: {} - # Affinity to apply - affinity: {} - # Tolerations to apply - tolerations: [] - # Extra Labels - labels: {} - # Extra Annotations - annotations: {} - podAnnotations: {} - podLabels: {} - resources: - requests: - cpu: 20m - memory: 150Mi - # Storage settings for the etcd - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - priorityClassName: "" - securityContext: {} - serviceAnnotations: {} - autoDeletePersistentVolumeClaims: false - -# Kubernetes Controller Manager settings -controller: - image: public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.28.2-eks-1-28-6 - imagePullPolicy: "" - -# Kubernetes API Server settings -api: - image: public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.28.2-eks-1-28-6 - imagePullPolicy: "" - extraArgs: [] - -# Core DNS settings -coredns: - # If CoreDns is enabled - enabled: true - # Pro only feature - integrated: false - # only used if isolation is enabled - fallback: 8.8.8.8 - plugin: - enabled: false - config: [] - # example configuration for plugin syntax, will be documented in detail - # - record: - # fqdn: google.com - # target: - # mode: url - # url: google.co.in - # - record: - # service: my-namespace/my-svc # dns-test/nginx-svc - # target: - # mode: host - # service: dns-test/nginx-svc - # - record: - # service: my-namespace-lb/my-svc-lb - # target: - # mode: host - # service: dns-test-exposed-lb/nginx-svc-exposed-lb - # - record: - # service: my-ns-external-name/my-svc-external-name - # target: - # mode: host - # service: dns-test-external-name/nginx-svc-external-name - # - record: - # service: my-ns-in-vcluster/my-svc-vcluster - # target: - # mode: vcluster # can be tested only manually for now - # vcluster: test-vcluster-ns/test-vcluster - # service: dns-test-in-vcluster-ns/test-in-vcluster-service - # - record: - # service: my-ns-in-vcluster-mns/my-svc-mns - # target: - # mode: vcluster # can be tested only manually for now - # service: dns-test-in-vcluster-mns/test-in-vcluster-svc-mns - # vcluster: test-vcluster-ns-mns/test-vcluster-mns - # - record: - # service: my-self-vc-ns/my-self-vc-svc - # target: - # mode: self - # service: dns-test/nginx-svc - image: public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-28-6 - replicas: 1 - # The nodeSelector example below specifices that coredns should only be scheduled to nodes with the arm64 label - # nodeSelector: - # kubernetes.io/arch: arm64 - # CoreDNS service configurations - service: - # Extra Annotations - annotations: {} - resources: - limits: - cpu: 1000m - memory: 512Mi - requests: - # ensure that cpu/memory requests are high enough. - # for example gke wants minimum 10m/32Mi here! - cpu: 20m - memory: 64Mi -# if below option is configured, it will override the coredns manifests with the following string -# manifests: |- -# apiVersion: ... -# ... - podAnnotations: {} - podLabels: {} - -# Service account that should be used by the vcluster -serviceAccount: - create: true - # Optional name of the service account to use - # name: default - # Optional pull secrets - # imagePullSecrets: - # - name: my-pull-secret - -# Service account that should be used by the pods synced by vcluster -workloadServiceAccount: - # This is not supported in multi-namespace mode - annotations: {} - -# Roles & ClusterRoles for the vcluster -rbac: - clusterRole: - # You don't need to toggle this as necessary cluster roles are created based on the enabled syncers (.sync.*.enabled). - # This only makes sense to enable if you want to use the extraRules below. - create: false - # Extra Rules for the cluster role - extraRules: [] - role: - # Disable this only if you don't want vCluster to create a role. This will break most functionality if disabled. - create: true - # Extra Rules for the role - extraRules: [] - # all entries in excludedApiResources will be excluded from the Role created for vcluster - excludedApiResources: - # - pods/exec - -# Syncer service configurations -service: - type: ClusterIP - - # Optional configuration - # A list of IP addresses for which nodes in the cluster will also accept traffic for this service. - # These IPs are not managed by Kubernetes; e.g., an external load balancer. - externalIPs: [] - - # Optional configuration for LoadBalancer & NodePort service types - # Route external traffic to node-local or cluster-wide endpoints [ Local | Cluster ] - externalTrafficPolicy: "" - - # Optional configuration for LoadBalancer service type - # Specify IP of load balancer to be created - loadBalancerIP: "" - # CIDR block(s) for the service allowlist - loadBalancerSourceRanges: [] - # Set the loadBalancerClass if using an external load balancer controller - loadBalancerClass: "" - # Set loadBalancer specific annotations on the Kubernetes service - loadBalancerAnnotations: {} - -# Configure the ingress resource that allows you to access the vcluster -ingress: - # Enable ingress record generation - enabled: false - # Ingress path type - pathType: ImplementationSpecific - ingressClassName: "" - host: vcluster.local - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTPS - nginx.ingress.kubernetes.io/ssl-passthrough: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - # Ingress TLS configuration - tls: [] - # - secretName: tls-vcluster.local - # hosts: - # - vcluster.local - -# Set "enable" to true when running vcluster in an OpenShift host -# This will add an extra rule to the deployed role binding in order -# to manage service endpoints -openshift: - enable: false - -# manifests to setup when initializing a vcluster -init: - manifests: |- - --- - # The contents of manifests-template will be templated using helm - # this allows you to use helm values inside, e.g.: {{ .Release.Name }} - manifestsTemplate: '' - helm: [] - # - bundle: - base64-encoded .tar.gz file content (optional - overrides chart.repo) - # chart: - # name: REQUIRED - # version: REQUIRED - # repo: (optional when bundle is used) - # username: (if required for repo) - # password: (if required for repo) - # insecure: boolean (if required for repo) - # release: - # name: REQUIRED - # namespace: REQUIRED - # timeout: number - # values: |- string YAML object - # foo: bar - # valuesTemplate: |- string YAML object - # foo: {{ .Release.Name }} - -# If enabled will deploy vcluster in an isolated mode with pod security -# standards, limit ranges and resource quotas -isolation: - enabled: false - namespace: null - - podSecurityStandard: baseline - - # If enabled will add node/proxy permission to the cluster role - # in isolation mode - nodeProxyPermission: - enabled: false - - resourceQuota: - enabled: true - quota: - requests.cpu: 10 - requests.memory: 20Gi - requests.storage: "100Gi" - requests.ephemeral-storage: 60Gi - limits.cpu: 20 - limits.memory: 40Gi - limits.ephemeral-storage: 160Gi - services.nodeports: 0 - services.loadbalancers: 1 - count/endpoints: 40 - count/pods: 20 - count/services: 20 - count/secrets: 100 - count/configmaps: 100 - count/persistentvolumeclaims: 20 - scopeSelector: - matchExpressions: - scopes: - - limitRange: - enabled: true - default: - ephemeral-storage: 8Gi - memory: 512Mi - cpu: "1" - defaultRequest: - ephemeral-storage: 3Gi - memory: 128Mi - cpu: 100m - - networkPolicy: - enabled: true - outgoingConnections: - ipBlock: - cidr: 0.0.0.0/0 - except: - - 100.64.0.0/10 - - 127.0.0.0/8 - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -multiNamespaceMode: - enabled: false - -# list of {validating/mutating}webhooks that the syncer should proxy. -# This is a PRO only feature. -centralAdmission: - validatingWebhooks: [] - mutatingWebhooks: [] - -telemetry: - disabled: false - instanceCreator: "helm" - platformUserID: "" - platformInstanceID: "" - machineID: "" diff --git a/charts/k0s/Chart.yaml b/charts/k0s/Chart.yaml deleted file mode 100644 index 228652bb5..000000000 --- a/charts/k0s/Chart.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v2 -name: vcluster-k0s -description: vcluster - Virtual Kubernetes Clusters (k0s) -home: https://vcluster.com -icon: https://static.loft.sh/branding/logos/vcluster/vertical/vcluster_vertical.svg -keywords: - - developer - - development - - sharing - - share - - multi-tenancy - - tenancy - - cluster - - space - - namespace - - vcluster - - vclusters -maintainers: - - name: Loft Labs, Inc. - email: info@loft.sh - url: https://twitter.com/loft_sh -sources: - - https://github.com/loft-sh/vcluster -type: application - -version: 0.0.1 # version is auto-generated by release pipeline diff --git a/charts/k0s/README.md b/charts/k0s/README.md deleted file mode 100644 index ae0e7b0c0..000000000 --- a/charts/k0s/README.md +++ /dev/null @@ -1,64 +0,0 @@ - -# vcluster (k0s) - -## **[GitHub](https://github.com/loft-sh/vcluster)** • **[Website](https://www.vcluster.com)** • **[Quickstart](https://www.vcluster.com/docs/getting-started/setup)** • **[Documentation](https://www.vcluster.com/docs/what-are-virtual-clusters)** • **[Blog](https://loft.sh/blog)** • **[Twitter](https://twitter.com/loft_sh)** • **[Slack](https://slack.loft.sh/)** - -Create fully functional virtual Kubernetes clusters - Each vcluster runs inside a namespace of the underlying k8s cluster. It's cheaper than creating separate full-blown clusters and it offers better multi-tenancy and isolation than regular namespaces. - -## Prerequisites - -- Kubernetes 1.18+ -- Helm 3+ - -## Get Helm Repository Info - -```bash -helm repo add loft-sh https://charts.loft.sh -helm repo update -``` - -See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation. - -## Install Helm Chart - -```bash -helm upgrade [RELEASE_NAME] loft-sh/vcluster-k0s -n [RELEASE_NAMESPACE] --create-namespace --install -``` - -See [vcluster docs](https://vcluster.com/docs) for configuration options. - -See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation. - -## Connect to the vcluster - -In order to connect to the installed vcluster, please install [vcluster cli](https://www.vcluster.com/docs/getting-started/setup) and run: - -```bash -vcluster connect [RELEASE_NAME] -n [RELEASE_NAMESPACE] -``` - -## Uninstall Helm Chart - -```bash -helm uninstall [RELEASE_NAME] -``` - -This removes all the Kubernetes components associated with the chart and deletes the release. - -See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation. - -### Why Virtual Kubernetes Clusters? - -- **Cluster Scoped Resources**: much more powerful than simple namespaces (virtual clusters allow users to use CRDs, namespaces, cluster roles etc.) -- **Ease of Use**: usable in any Kubernetes cluster and created in seconds either via a single command or [cluster-api](https://github.com/loft-sh/cluster-api-provider-vcluster) -- **Cost Efficient**: much cheaper and efficient than "real" clusters (single pod and shared resources just like for namespaces) -- **Lightweight**: built upon the ultra-fast k3s distribution with minimal overhead per virtual cluster (other distributions work as well) -- **Strict isolation**: complete separate Kubernetes control plane and access point for each vcluster while still being able to share certain services of the underlying host cluster -- **Cluster Wide Permissions**: allow users to install apps which require cluster-wide permissions while being limited to actually just one namespace within the host cluster -- **Great for Testing**: allow you to test different Kubernetes versions inside a single host cluster which may have a different version than the virtual clusters - -Learn more on [www.vcluster.com](https://vcluster.com). - -![vcluster Intro](https://github.com/loft-sh/vcluster/raw/main/docs/static/media/vcluster-comparison.png) - -Learn more in the [documentation](https://vcluster.com/docs/what-are-virtual-clusters). diff --git a/charts/k0s/templates/NOTES.txt b/charts/k0s/templates/NOTES.txt deleted file mode 100644 index 6cf960d62..000000000 --- a/charts/k0s/templates/NOTES.txt +++ /dev/null @@ -1,10 +0,0 @@ -Thank you for installing vcluster. - -Your vcluster is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}. - -To connect to the vcluster, use vcluster CLI (https://www.vcluster.com/docs/getting-started/setup): - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} -- kubectl get ns - - -For more information, please take a look at the vcluster docs at https://www.vcluster.com/docs diff --git a/charts/k0s/templates/_coredns.tpl b/charts/k0s/templates/_coredns.tpl deleted file mode 100644 index c41f016f5..000000000 --- a/charts/k0s/templates/_coredns.tpl +++ /dev/null @@ -1,52 +0,0 @@ -{{/* - Define a common coredns config -*/}} -{{- define "vcluster.corefile" -}} -Corefile: |- - {{- if .Values.coredns.config }} -{{ .Values.coredns.config | indent 8 }} - {{- else }} - .:1053 { - errors - health - ready - rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local - kubernetes cluster.local in-addr.arpa ip6.arpa { - {{- if .Values.pro }} - {{- if .Values.coredns.integrated }} - kubeconfig /data/k0s/pki/admin.conf - {{- end }} - {{- end }} - pods insecure - {{- if .Values.fallbackHostDns }} - fallthrough cluster.local in-addr.arpa ip6.arpa - {{- else }} - fallthrough in-addr.arpa ip6.arpa - {{- end }} - } - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - vcluster {{ toYaml .Values.coredns.plugin.config | b64enc }} - {{- end }} - hosts /etc/NodeHosts { - ttl 60 - reload 15s - fallthrough - } - prometheus :9153 - {{- if .Values.fallbackHostDns }} - forward . {{`{{.HOST_CLUSTER_DNS}}`}} - {{- else if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} - forward . /etc/resolv.conf {{ .Values.coredns.fallback }} { - policy sequential - } - {{- else }} - forward . /etc/resolv.conf - {{- end }} - cache 30 - loop - loadbalance - } - - import /etc/coredns/custom/*.server - {{- end }} -{{- end -}} diff --git a/charts/k0s/templates/_helpers.tpl b/charts/k0s/templates/_helpers.tpl deleted file mode 100644 index 50f08d6f9..000000000 --- a/charts/k0s/templates/_helpers.tpl +++ /dev/null @@ -1,196 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "vcluster.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Whether the ingressclasses syncer should be enabled -*/}} -{{- define "vcluster.syncIngressclassesEnabled" -}} -{{- if or - (.Values.sync.ingressclasses).enabled - (and - .Values.sync.ingresses.enabled - (not .Values.sync.ingressclasses)) -}} - {{- true -}} -{{- end -}} -{{- end -}} - -{{/* -Whether to create a cluster role or not -*/}} -{{- define "vcluster.createClusterRole" -}} -{{- if or - (not (empty (include "vcluster.serviceMapping.fromHost" . ))) - (not (empty (include "vcluster.plugin.clusterRoleExtraRules" . ))) - (not (empty (include "vcluster.generic.clusterRoleExtraRules" . ))) - .Values.rbac.clusterRole.create - .Values.sync.hoststorageclasses.enabled - (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") - (include "vcluster.syncIngressclassesEnabled" . ) - .Values.pro - .Values.sync.nodes.enabled - .Values.sync.persistentvolumes.enabled - .Values.sync.storageclasses.enabled - .Values.sync.priorityclasses.enabled - .Values.sync.volumesnapshots.enabled - .Values.proxy.metricsServer.nodes.enabled - .Values.multiNamespaceMode.enabled - .Values.coredns.plugin.enabled -}} -{{- true -}} -{{- end -}} -{{- end -}} - -{{- define "vcluster.clusterRoleName" -}} -{{- printf "vc-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "vcluster.clusterRoleNameMultinamespace" -}} -{{- printf "vc-mn-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Syncer flags for enabling/disabling controllers -Prints only the flags that modify the defaults: -- when default controller has enabled: false => `- "--sync=-controller` -- when non-default controller has enabled: true => `- "--sync=controller` -*/}} -{{- define "vcluster.syncer.syncArgs" -}} -{{- $defaultEnabled := list "services" "configmaps" "secrets" "endpoints" "pods" "events" "persistentvolumeclaims" "fake-nodes" "fake-persistentvolumes" -}} -{{- if and (hasKey .Values.sync.nodes "enableScheduler") .Values.sync.nodes.enableScheduler -}} - {{- $defaultEnabled = concat $defaultEnabled (list "csinodes" "csidrivers" "csistoragecapacities" ) -}} -{{- end -}} -{{- range $key, $val := .Values.sync }} -{{- if and (has $key $defaultEnabled) (not $val.enabled) }} -- --sync=-{{ $key }} -{{- else if and (not (has $key $defaultEnabled)) ($val.enabled)}} -{{- if eq $key "legacy-storageclasses" }} -- --sync=hoststorageclasses -{{- else }} -- --sync={{ $key }} -{{- end -}} -{{- end -}} -{{- end -}} -{{- if not (include "vcluster.syncIngressclassesEnabled" . ) }} -- --sync=-ingressclasses -{{- end -}} -{{- end -}} - -{{/* - Cluster role rules defined by plugins -*/}} -{{- define "vcluster.plugin.clusterRoleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.clusterRole }} -{{- if $container.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.clusterRoleExtraRules" -}} -{{- if .Values.sync.generic.clusterRole }} -{{- if .Values.sync.generic.clusterRole.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined on global level -*/}} -{{- define "vcluster.rbac.clusterRoleExtraRules" -}} -{{- if .Values.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - - -{{/* - Role rules defined on global level -*/}} -{{- define "vcluster.rbac.roleExtraRules" -}} -{{- if .Values.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined by plugins -*/}} -{{- define "vcluster.plugin.roleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.role }} -{{- if $container.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.roleExtraRules" -}} -{{- if .Values.sync.generic.role }} -{{- if .Values.sync.generic.role.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Virtual cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromVirtual" -}} -{{- range $key, $value := .Values.mapServices.fromVirtual }} -- '--map-virtual-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} - -{{/* - Host cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromHost" -}} -{{- range $key, $value := .Values.mapServices.fromHost }} -- '--map-host-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} diff --git a/charts/k0s/templates/_migrate.tpl b/charts/k0s/templates/_migrate.tpl deleted file mode 100644 index 34bef9a19..000000000 --- a/charts/k0s/templates/_migrate.tpl +++ /dev/null @@ -1,6 +0,0 @@ -{{/* - handles both replicas and syncer.replicas -*/}} -{{- define "vcluster.replicas" -}} -{{ if .Values.replicas }}{{ .Values.replicas }}{{ else }}{{ .Values.syncer.replicas }}{{ end }} -{{- end }} diff --git a/charts/k0s/templates/_storage.tpl b/charts/k0s/templates/_storage.tpl deleted file mode 100644 index b10675ab6..000000000 --- a/charts/k0s/templates/_storage.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{{/* - storage size -*/}} -{{- define "vcluster.storage.size" -}} -{{if .Values.storage }}{{ .Values.storage.size }}{{ else }}{{ .Values.syncer.storage.size }}{{ end }} -{{- end -}} - -{{/* - storage persistence -*/}} -{{- define "vcluster.storage.persistence" -}} -{{if .Values.storage }}{{ .Values.storage.persistence }}{{ else }}{{ .Values.syncer.storage.persistence }}{{ end }} -{{- end -}} - -{{/* - storage classname -*/}} -{{- define "vcluster.storage.className" -}} -{{if .Values.storage }}{{ .Values.storage.className }}{{ else }}{{ .Values.syncer.storage.className }}{{ end }} -{{- end -}} diff --git a/charts/k0s/templates/ingress.yaml b/charts/k0s/templates/ingress.yaml deleted file mode 100644 index d629f096f..000000000 --- a/charts/k0s/templates/ingress.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - {{- $annotations := merge .Values.ingress.annotations .Values.globalAnnotations }} - {{- if .Values.ingress.tls }} - {{- $annotations = omit $annotations "nginx.ingress.kubernetes.io/ssl-passthrough" }} - {{- end }} - {{- if $annotations }} - annotations: - {{- toYaml $annotations | nindent 4 }} - {{- end }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - {{- if .Values.ingress.ingressClassName }} - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} - {{- end }} - rules: - - host: {{ .Values.ingress.host | quote }} - http: - paths: - - backend: - service: - name: {{ .Release.Name }} - port: - name: https - path: / - pathType: {{ .Values.ingress.pathType }} - {{- with .Values.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end -}} -{{- end }} diff --git a/charts/k0s/templates/init-configmap.yaml b/charts/k0s/templates/init-configmap.yaml deleted file mode 100644 index 34498fd14..000000000 --- a/charts/k0s/templates/init-configmap.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.init.manifests }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-init-manifests - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: - manifests: |- - {{ .Values.init.manifests | nindent 4 | trim }} - {{ tpl .Values.init.manifestsTemplate $ | nindent 4 | trim }} - {{- if .Values.init.helm }} - charts: |- - {{- range .Values.init.helm }} - {{- /* only render this chart entry if either of chart or bundle is defined */}} - {{- if .chart }} - - name: {{ .chart.name }} - repo: {{ .chart.repo }} - version: {{ .chart.version }} - {{- if .chart.username }} - username: {{ .chart.username }} - {{- end }} - {{- if .chart.password }} - password: {{ .chart.password }} - {{- end }} - {{- if .insecure }} - insecure: true - {{- end}} - {{- end }} - {{- if .bundle }} - bundle: {{ .bundle }} - {{- end }} - {{- if or .chart .bundle }} - {{- if or .values .valuesTemplate }} - values: |- - {{ (.values | default "") | nindent 8 | trim }} - {{ tpl (.valuesTemplate | default "") $ | nindent 8 | trim }} - {{- end}} - {{- if .release }} - timeout: {{ .timeout | default "120s" | quote }} - releaseName: {{ .release.name }} - releaseNamespace: {{ .release.namespace }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k0s/templates/integrated-coredns.yaml b/charts/k0s/templates/integrated-coredns.yaml deleted file mode 100644 index 16907cba0..000000000 --- a/charts/k0s/templates/integrated-coredns.yaml +++ /dev/null @@ -1,64 +0,0 @@ -{{- if .Values.pro }} -{{- if .Values.coredns.integrated }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-dns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{ include "vcluster.corefile" . | indent 2 }} - coredns.yaml: |- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: - NodeHosts: "" - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} diff --git a/charts/k0s/templates/limitrange.yaml b/charts/k0s/templates/limitrange.yaml deleted file mode 100644 index ed219d063..000000000 --- a/charts/k0s/templates/limitrange.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.limitRange.enabled }} -apiVersion: v1 -kind: LimitRange -metadata: - name: {{ .Release.Name }}-limit-range - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - limits: - - default: - {{- range $key, $val := .Values.isolation.limitRange.default }} - {{ $key }}: {{ $val | quote }} - {{- end }} - defaultRequest: - {{- range $key, $val := .Values.isolation.limitRange.defaultRequest }} - {{ $key }}: {{ $val | quote }} - {{- end }} - type: Container -{{- end }} diff --git a/charts/k0s/templates/networkpolicy.yaml b/charts/k0s/templates/networkpolicy.yaml deleted file mode 100644 index c932065e0..000000000 --- a/charts/k0s/templates/networkpolicy.yaml +++ /dev/null @@ -1,70 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-workloads - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} -spec: - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - egress: - # Allows outgoing connections to the vcluster control plane - - ports: - - port: 443 - - port: 8443 - to: - - podSelector: - matchLabels: - release: {{ .Release.Name }} - # Allows outgoing connections to DNS server - - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP - # Allows outgoing connections to the internet or - # other vcluster workloads - - to: - - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - - ipBlock: - cidr: {{ .Values.isolation.networkPolicy.outgoingConnections.ipBlock.cidr }} - except: - {{- range .Values.isolation.networkPolicy.outgoingConnections.ipBlock.except }} - - {{ . }} - {{- end }} - policyTypes: - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-control-plane - namespace: {{ .Release.Namespace }} -spec: - podSelector: - matchLabels: - release: {{ .Release.Name }} - egress: - # Allows outgoing connections to all pods with - # port 443, 8443 or 6443. This is needed for host Kubernetes - # access - - ports: - - port: 443 - - port: 8443 - - port: 6443 - # Allows outgoing connections to all vcluster workloads - # or kube system dns server - - to: - - podSelector: {} - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: 'kube-system' - podSelector: - matchLabels: - k8s-app: kube-dns - policyTypes: - - Egress -{{- end }} \ No newline at end of file diff --git a/charts/k0s/templates/rbac/clusterrole.yaml b/charts/k0s/templates/rbac/clusterrole.yaml deleted file mode 100644 index 984267cae..000000000 --- a/charts/k0s/templates/rbac/clusterrole.yaml +++ /dev/null @@ -1,103 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: -{{- if .Values.pro }} - - apiGroups: ["cluster.loft.sh", "storage.loft.sh"] - resources: ["features", "virtualclusters"] - verbs: ["get", "list", "watch"] -{{- end }} - {{- if or .Values.pro .Values.sync.nodes.enabled }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["get", "watch", "list"] - - apiGroups: [""] - resources: [ "pods", "nodes/metrics", "nodes/stats"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.coredns.plugin.enabled }} - - apiGroups: [""] - resources: [ "pods"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled (or (not .Values.isolation.enabled) (and .Values.isolation.nodeProxyPermission.enabled .Values.isolation.enabled)) }} - - apiGroups: [""] - resources: ["nodes/proxy"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled .Values.sync.nodes.syncNodeChanges }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["update", "patch"] - {{- end }} - {{- if .Values.sync.persistentvolumes.enabled }} - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if .Values.sync.nodes.enableScheduler }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses","csinodes","csidrivers","csistoragecapacities"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if (include "vcluster.syncIngressclassesEnabled" . ) }} - - apiGroups: ["networking.k8s.io"] - resources: ["ingressclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.storageclasses.enabled }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if or .Values.sync.hoststorageclasses.enabled (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") .Values.rbac.clusterRole.create }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.priorityclasses.enabled }} - - apiGroups: ["scheduling.k8s.io"] - resources: ["priorityclasses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if (not (empty (include "vcluster.serviceMapping.fromHost" . ))) }} - - apiGroups: [""] - resources: ["services", "endpoints"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.proxy.metricsServer.nodes.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["nodes"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.clusterRoleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/k0s/templates/rbac/clusterrolebinding.yaml b/charts/k0s/templates/rbac/clusterrolebinding.yaml deleted file mode 100644 index 0a5645d28..000000000 --- a/charts/k0s/templates/rbac/clusterrolebinding.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: - kind: ClusterRole - name: {{ template "vcluster.clusterRoleName" . }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/k0s/templates/rbac/role.yaml b/charts/k0s/templates/rbac/role.yaml deleted file mode 100644 index de05bbb62..000000000 --- a/charts/k0s/templates/rbac/role.yaml +++ /dev/null @@ -1,104 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRole -{{- else -}} -kind: Role -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} -{{- end }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: - {{- if .Values.pro }} - - apiGroups: [""] - {{- $resources := list "configmaps" "secrets" "services" "pods" "pods/attach" "pods/portforward" "pods/exec" "persistentvolumeclaims" }} - {{- range $excluded := .Values.rbac.role.excludedApiResources }} - {{- $resources = without $resources $excluded }} - {{- end}} - resources: {{ $resources | toJson }} - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- else }} - - apiGroups: [""] - resources: ["configmaps", "secrets", "services", "pods", "pods/attach", "pods/portforward", "pods/exec", "persistentvolumeclaims"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.status }} - - apiGroups: [""] - resources: ["pods/status"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.ephemeralContainers }} - - apiGroups: [""] - resources: ["pods/ephemeralcontainers"] - verbs: ["patch", "update"] - {{- end }} - {{- if or .Values.sync.endpoints.enabled .Values.headless }} - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["create", "delete", "patch", "update"] - {{- end }} - {{- if gt (int .Values.syncer.replicas) 1 }} - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: [""] - resources: ["endpoints", "events", "pods/log"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.ingresses.enabled}} - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: ["apps"] - resources: ["statefulsets", "replicasets", "deployments"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.networkpolicies.enabled }} - - apiGroups: ["networking.k8s.io"] - resources: ["networkpolicies"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.serviceaccounts.enabled }} - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.poddisruptionbudgets.enabled }} - - apiGroups: ["policy"] - resources: ["poddisruptionbudgets"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.openshift.enable }} - {{- if .Values.sync.endpoints.enabled }} - - apiGroups: [""] - resources: ["endpoints/restricted"] - verbs: ["create"] - {{- end }} - {{- end }} - {{- if .Values.proxy.metricsServer.pods.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["pods"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.roleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.roleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.roleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/k0s/templates/rbac/rolebinding.yaml b/charts/k0s/templates/rbac/rolebinding.yaml deleted file mode 100644 index 676e5002a..000000000 --- a/charts/k0s/templates/rbac/rolebinding.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRoleBinding -{{- else -}} -kind: RoleBinding -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -{{- end }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: -{{- if .Values.multiNamespaceMode.enabled }} - kind: ClusterRole - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - kind: Role - name: {{ .Release.Name }} -{{- end }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/k0s/templates/resourcequota.yaml b/charts/k0s/templates/resourcequota.yaml deleted file mode 100644 index b19d89fff..000000000 --- a/charts/k0s/templates/resourcequota.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.resourceQuota.enabled }} -apiVersion: v1 -kind: ResourceQuota -metadata: - name: {{ .Release.Name }}-quota - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - hard: - {{- range $key, $val := .Values.isolation.resourceQuota.quota }} - {{ $key }}: {{ $val | quote }} - {{- end }} - - {{- if .Values.isolation.resourceQuota.scopeSelector.matchExpressions }} - scopeSelector: - matchExpressions: - {{- toYaml .Values.isolation.resourceQuota.scopeSelector.matchExpressions | nindent 4 }} - {{- end}} - - {{- if .Values.isolation.resourceQuota.scopes }} - scopes: - {{- toYaml .Values.isolation.resourceQuota.scopes | nindent 4 }} - {{- end}} - -{{- end }} diff --git a/charts/k0s/templates/secret.yaml b/charts/k0s/templates/secret.yaml deleted file mode 100644 index 2b55f5caa..000000000 --- a/charts/k0s/templates/secret.yaml +++ /dev/null @@ -1,65 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: v1 -kind: Secret -metadata: - name: vc-{{ .Release.Name }}-config - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations}} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -type: Opaque -stringData: - {{- if .Values.config }} - config.yaml: {{ toJson .Values.config }} - {{- else }} - config.yaml: |- - apiVersion: k0s.k0sproject.io/v1beta1 - kind: Cluster - metadata: - name: k0s - spec: - api: - port: 6443 - k0sApiPort: 9443 - extraArgs: - bind-address: 127.0.0.1 - enable-admission-plugins: NodeRestriction - endpoint-reconciler-type: none - network: - {{- if .Values.serviceCIDR }} - serviceCIDR: {{ .Values.serviceCIDR }} - {{- else }} - # Will be replaced automatically by the syncer container on first startup - serviceCIDR: CIDR_PLACEHOLDER - {{- end }} - provider: custom - {{- if .Values.vcluster.clusterDomain }} - clusterDomain: {{ .Values.vcluster.clusterDomain }} - {{- end}} - controllerManager: - extraArgs: - {{- if not .Values.sync.nodes.enableScheduler }} - controllers: '*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' - {{- else }} - controllers: '*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' - node-monitor-grace-period: 1h - node-monitor-period: 1h - {{- end }} - {{- if .Values.embeddedEtcd.enabled }} - storage: - etcd: - externalCluster: - endpoints: ["127.0.0.1:2379"] - caFile: /data/k0s/pki/etcd/ca.crt - etcdPrefix: "/registry" - clientCertFile: /data/k0s/pki/apiserver-etcd-client.crt - clientKeyFile: /data/k0s/pki/apiserver-etcd-client.key - {{- end }} - {{- end }} - {{- end }} diff --git a/charts/k0s/templates/service-monitor.yaml b/charts/k0s/templates/service-monitor.yaml deleted file mode 100644 index b6917ae27..000000000 --- a/charts/k0s/templates/service-monitor.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if .Values.monitoring.serviceMonitor.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - selector: - matchLabels: - app: vcluster - release: "{{ .Release.Name }}" - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - heritage: "{{ .Release.Service }}" - endpoints: - - interval: 30s - port: https - path: /metrics - scheme: https - tlsConfig: - ca: - secret: - name: vc-{{ .Release.Name }} - key: certificate-authority - cert: - secret: - name: vc-{{ .Release.Name }} - key: client-certificate - keySecret: - name: vc-{{ .Release.Name }} - key: client-key - serverName: 127.0.0.1 -{{- end }} diff --git a/charts/k0s/templates/service.yaml b/charts/k0s/templates/service.yaml deleted file mode 100644 index b575a9fe1..000000000 --- a/charts/k0s/templates/service.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: {{ if eq .Values.service.type "LoadBalancer" -}}ClusterIP{{- else }} {{- .Values.service.type }} {{- end }} - ports: - - name: https - port: 443 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.httpsNodePort }} - protocol: TCP - - name: kubelet - port: 10250 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.kubeletNodePort }} - protocol: TCP - {{- if .Values.service.externalIPs }} - externalIPs: - {{- range $f := .Values.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- if eq .Values.service.type "NodePort" }} - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} ---- -{{ if eq .Values.service.type "LoadBalancer" }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-lb - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.service.loadBalancerAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: LoadBalancer - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} - {{- if .Values.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.service.loadBalancerIP }} - {{- end }} - {{- if .Values.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.service.loadBalancerClass }} - {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: - {{- range $f := .Values.service.loadBalancerSourceRanges }} - - "{{ $f }}" - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k0s/templates/serviceaccount.yaml b/charts/k0s/templates/serviceaccount.yaml deleted file mode 100644 index 504998037..000000000 --- a/charts/k0s/templates/serviceaccount.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} -{{- end }} diff --git a/charts/k0s/templates/statefulset-service.yaml b/charts/k0s/templates/statefulset-service.yaml deleted file mode 100644 index 7523bf36e..000000000 --- a/charts/k0s/templates/statefulset-service.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-headless - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "vcluster.fullname" . }} - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - publishNotReadyAddresses: true - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.embeddedEtcd.enabled }} - - name: etcd - port: 2379 - targetPort: 2379 - protocol: TCP - - name: peer - port: 2380 - targetPort: 2380 - protocol: TCP - {{- end }} - clusterIP: None - selector: - app: vcluster - release: "{{ .Release.Name }}" -{{- end }} diff --git a/charts/k0s/templates/syncer.yaml b/charts/k0s/templates/syncer.yaml deleted file mode 100644 index 965c975c9..000000000 --- a/charts/k0s/templates/syncer.yaml +++ /dev/null @@ -1,348 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -{{- if .Values.labels }} -{{ toYaml .Values.labels | indent 4 }} -{{- end }} -{{- $annotations := merge .Values.annotations .Values.globalAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - serviceName: {{ .Release.Name }}-headless - {{- if .Values.autoDeletePersistentVolumeClaims }} - {{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} - persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete - {{- end }} - {{- end }} - replicas: {{ include "vcluster.replicas" . }} - selector: - matchLabels: - app: vcluster - release: {{ .Release.Name }} - {{- if (hasKey .Values "volumeClaimTemplates") }} - volumeClaimTemplates: -{{ toYaml .Values.volumeClaimTemplates | indent 4 }} - {{- else if ( eq ( include "vcluster.storage.persistence" . ) "true" ) }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - {{- if (include "vcluster.storage.className" . ) }} - storageClassName: {{ include "vcluster.storage.className" . }} - {{- end }} - resources: - requests: - storage: {{ include "vcluster.storage.size" . }} - {{- end }} - template: - metadata: - {{- if .Values.podAnnotations }} - annotations: -{{ toYaml .Values.podAnnotations | indent 8 }} - {{- end }} - labels: - app: vcluster - release: {{ .Release.Name }} - {{- range $k, $v := .Values.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - terminationGracePeriodSeconds: 10 - nodeSelector: -{{ toYaml .Values.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.tolerations | indent 8 }} - {{- if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} - {{- else }} - serviceAccountName: vc-{{ .Release.Name }} - {{- end }} - volumes: - {{- include "vcluster.plugins.volumes" . | indent 8 }} - - name: helm-cache - emptyDir: {} - - name: tmp - emptyDir: {} - - name: run-k0s - emptyDir: {} - - name: binaries - emptyDir: {} - {{- if .Values.syncer.volumes }} -{{ toYaml .Values.syncer.volumes | indent 8 }} - {{- end }} - {{- if .Values.volumes }} -{{ toYaml .Values.volumes | indent 8 }} - {{- end }} - {{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} - - name: coredns - configMap: - name: {{ .Release.Name }}-coredns - {{- else if .Values.coredns.integrated }} - - name: coredns - configMap: - name: {{ .Release.Name }}-dns - {{- end }} - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - {{- if not (eq ( include "vcluster.storage.persistence" . ) "true" ) }} - - name: data - emptyDir: {} - {{- end }} - {{- if .Values.priorityClassName }} - priorityClassName: {{ .Values.priorityClassName }} - {{- end }} - {{- if or .Values.podSecurityContext .Values.fsGroup }} - {{- $fsContext := dict "fsGroup" .Values.fsGroup }} - {{- with merge .Values.podSecurityContext $fsContext }} - securityContext: -{{ toYaml . | indent 8 }} - {{- end }} - {{- end }} - initContainers: - {{- include "vcluster.plugins.initContainers" . | indent 6 }} - - image: {{ .Values.defaultImageRegistry }}{{ .Values.vcluster.image }} - name: vcluster - command: - - /bin/sh - args: - - -c - - "cp /usr/local/bin/k0s /binaries/k0s" - {{- if .Values.vcluster.imagePullPolicy }} - imagePullPolicy: {{ .Values.vcluster.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.securityContext | indent 10 }} - volumeMounts: - - name: binaries - mountPath: /binaries - resources: -{{ toYaml .Values.vcluster.resources | indent 10 }} - containers: - {{- if not .Values.syncer.disabled }} - - name: syncer - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - {{- if .Values.syncer.workingDir }} - workingDir: {{ .Values.syncer.workingDir }} - {{- end }} - {{- if .Values.syncer.command }} - command: - {{- range $f := .Values.syncer.command }} - - {{ $f | quote }} - {{- end }} - {{- end }} - args: - - --name={{ .Release.Name }} - - --service-account=vc-workload-{{ .Release.Name }} - - --request-header-ca-cert=/data/k0s/pki/front-proxy-ca.crt - - --client-ca-cert=/data/k0s/pki/ca.crt - - --server-ca-cert=/data/k0s/pki/ca.crt - - --server-ca-key=/data/k0s/pki/ca.key - - --kube-config=/data/k0s/pki/admin.conf - {{- if and .Values.embeddedEtcd.enabled .Values.pro }} - - --etcd-embedded - - --etcd-replicas={{ .Values.syncer.replicas }} - {{- end }} - {{- if (gt (int .Values.syncer.replicas ) 1) }} - - --leader-elect=true - {{- else }} - - --leader-elect=false - {{- end }} - {{- include "vcluster.legacyPlugins.args" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromHost" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromVirtual" . | indent 10 }} - {{- if .Values.sync.nodes.enableScheduler }} - - --enable-scheduler - {{- end }} - {{- if .Values.pro }} - {{- if .Values.proLicenseSecret }} - - --pro-license-secret={{ .Values.proLicenseSecret }} - {{- end }} - {{- end }} - {{- if .Values.defaultImageRegistry }} - - --default-image-registry={{ .Values.defaultImageRegistry }} - {{- end }} - {{- if .Values.syncer.kubeConfigContextName }} - - --kube-config-context-name={{ .Values.syncer.kubeConfigContextName }} - {{- end }} - {{- if .Values.ingress.enabled }} - - --tls-san={{ .Values.ingress.host }} - {{- end }} - {{- if .Values.isolation.enabled }} - - --enforce-pod-security-standard={{ .Values.isolation.podSecurityStandard }} - {{- end}} - {{- include "vcluster.syncer.syncArgs" . | indent 10 -}} - {{- if .Values.sync.nodes.syncAllNodes }} - - --sync-all-nodes - {{- end }} - {{- if .Values.sync.nodes.nodeSelector }} - - --node-selector={{ .Values.sync.nodes.nodeSelector }} - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - --multi-namespace-mode=true - {{- end }} - {{- if .Values.sync.configmaps.all }} - - --sync-all-configmaps=true - {{- end }} - {{- if .Values.sync.secrets.all }} - - --sync-all-secrets=true - {{- end }} - {{- if not .Values.sync.nodes.fakeKubeletIPs }} - - --fake-kubelet-ips=false - {{- end }} - {{- if or .Values.proxy.metricsServer.nodes.enabled .Values.proxy.metricsServer.pods.enabled }} - - --proxy-metrics-server=true - {{- end }} - {{- if .Values.coredns.integrated }} - - --integrated-coredns=true - {{- end }} - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - - --use-coredns-plugin=true - {{- end }} - {{- if .Values.centralAdmission.validatingWebhooks }} - {{- range .Values.centralAdmission.validatingWebhooks }} - - --enforce-validating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- if .Values.centralAdmission.mutatingWebhooks }} - {{- range .Values.centralAdmission.mutatingWebhooks }} - - --enforce-mutating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- range $f := .Values.syncer.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- if .Values.syncer.livenessProbe }} - {{- if .Values.syncer.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: /healthz - port: 8443 - scheme: HTTPS - failureThreshold: 10 - initialDelaySeconds: 60 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.readinessProbe }} - {{- if .Values.syncer.readinessProbe.enabled }} - startupProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 300 - periodSeconds: 6 - readinessProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 30 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.securityContext | indent 10 }} - env: - {{- include "vcluster.plugins.config" . | indent 10 }} - - name: VCLUSTER_DISTRO - value: k0s - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if eq ( ( include "vcluster.replicas" . ) | toString | atoi) 1 }} - - name: VCLUSTER_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - {{- end }} - {{- if .Values.vcluster.env }} -{{ toYaml .Values.vcluster.env | indent 10 }} - {{- end }} - - name: ETCD_UNSUPPORTED_ARCH - value: arm64 - - name: VCLUSTER_COMMAND - value: |- - command: - {{- range $f := .Values.vcluster.command }} - - {{ $f | quote }} - {{- end }} - args: - {{- range $f := .Values.vcluster.baseArgs }} - - {{ $f | quote }} - {{- end }} - - --status-socket=/run/k0s/status.sock - {{- if not .Values.sync.nodes.enableScheduler }} - - --disable-components=konnectivity-server,kube-scheduler,csr-approver,kube-proxy,coredns,network-provider,helm,metrics-server,worker-config - {{- else }} - - --disable-components=konnectivity-server,csr-approver,kube-proxy,coredns,network-provider,helm,metrics-server,worker-config - {{- end }} - {{- range $f := .Values.vcluster.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- if .Values.syncer.env }} -{{ toYaml .Values.syncer.env | indent 10 }} - {{- end }} - {{- if .Values.sync.generic.config }} - - name: CONFIG - value: |- - {{- .Values.sync.generic.config | nindent 14 }} - {{- end }} - - name: VCLUSTER_TELEMETRY_CONFIG - value: {{ .Values.telemetry | toJson | quote }} - volumeMounts: - {{- include "vcluster.plugins.volumeMounts" . | indent 10 }} - - name: helm-cache - mountPath: /.cache/helm - - name: binaries - mountPath: /binaries - - mountPath: /data - name: data - - name: run-k0s - mountPath: /run/k0s - - name: tmp - mountPath: /tmp - {{- if .Values.coredns.enabled }} - - name: coredns - mountPath: /manifests/coredns - readOnly: true - {{- end }} - {{- if .Values.vcluster.volumeMounts }} -{{ toYaml .Values.vcluster.volumeMounts | indent 10 }} - {{- end }} - {{- if .Values.syncer.extraVolumeMounts }} -{{ toYaml .Values.syncer.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} -{{- include "vcluster.legacyPlugins.containers" . | indent 6 }} -{{- end }} diff --git a/charts/k0s/templates/workloadserviceaccount.yaml b/charts/k0s/templates/workloadserviceaccount.yaml deleted file mode 100644 index 8d05fc1e8..000000000 --- a/charts/k0s/templates/workloadserviceaccount.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-workload-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.workloadServiceAccount.annotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} diff --git a/charts/k0s/tests/README.md b/charts/k0s/tests/README.md deleted file mode 100644 index e6d4fa1c8..000000000 --- a/charts/k0s/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Add [unittest plugin](https://github.com/helm-unittest/helm-unittest) via: -``` -helm plugin install https://github.com/helm-unittest/helm-unittest.git -``` - -Run tests via: -``` -helm unittest charts/k0s -d -``` diff --git a/charts/k0s/tests/clusterrole_test.yaml b/charts/k0s/tests/clusterrole_test.yaml deleted file mode 100644 index 3385c786c..000000000 --- a/charts/k0s/tests/clusterrole_test.yaml +++ /dev/null @@ -1,41 +0,0 @@ -suite: ClusterRole -templates: - - rbac/clusterrole.yaml - -tests: - - it: should create clusterrole - set: - rbac: - clusterRole: - create: true - asserts: - - hasDocuments: - count: 1 - - it: should not create clusterrole - set: - rbac: - clusterRole: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - clusterRole: - create: true - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k0s/tests/legacy-plugins_test.yaml b/charts/k0s/tests/legacy-plugins_test.yaml deleted file mode 100644 index 6977c7562..000000000 --- a/charts/k0s/tests/legacy-plugins_test.yaml +++ /dev/null @@ -1,77 +0,0 @@ -suite: Legacy Plugins -templates: - - syncer.yaml - -tests: - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - bootstrap-with-deployment2: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment2" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[2].name - value: bootstrap-with-deployment2 - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[2].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - equal: - path: spec.template.spec.containers[2].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14001 - - - it: should check no legacy plugin rendering - asserts: - - hasDocuments: - count: 1 - - lengthEqual: - path: spec.template.spec.containers - count: 1 diff --git a/charts/k0s/tests/plugins_test.yaml b/charts/k0s/tests/plugins_test.yaml deleted file mode 100644 index 2ce3149dc..000000000 --- a/charts/k0s/tests/plugins_test.yaml +++ /dev/null @@ -1,100 +0,0 @@ -suite: Plugins -templates: - - syncer.yaml - -tests: - - it: should check plugin config rendering - set: - plugin: - plugin1: - version: v2 - config: - myConfig: true - plugin2: - version: v2 - image: test - plugin3: - version: v2 - image: test123 - config: - myOtherConfig: - - test123 - - test456 - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.containers[0].env[0].name - value: PLUGIN_CONFIG - - equal: - path: spec.template.spec.containers[0].env[0].value - value: |- - plugin1: - myConfig: true - plugin3: - myOtherConfig: - - test123 - - test456 - - - it: should check plugin rendering - set: - plugin: - bootstrap-with-deployment: - version: v2 - image: test - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.volumes[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins diff --git a/charts/k0s/tests/role_test.yaml b/charts/k0s/tests/role_test.yaml deleted file mode 100644 index 5eeedba74..000000000 --- a/charts/k0s/tests/role_test.yaml +++ /dev/null @@ -1,32 +0,0 @@ -suite: Role -templates: - - rbac/role.yaml - -tests: - - it: should not create role - set: - rbac: - role: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - role: - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k0s/tests/syncer_test.yaml b/charts/k0s/tests/syncer_test.yaml deleted file mode 100644 index 685792854..000000000 --- a/charts/k0s/tests/syncer_test.yaml +++ /dev/null @@ -1,65 +0,0 @@ -suite: Syncer -templates: - - syncer.yaml - -tests: - - it: should pass pro license secret as a flag - set: - pro: true - proLicenseSecret: "my-test-secret" - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--pro-license-secret=my-test-secret" - count: 1 - - it: should have pvc if persistence is on - set: - storage: - persistence: true - - asserts: - - hasDocuments: - count: 1 - - isNotEmpty: - path: spec.volumeClaimTemplates - - - it: should not have pvc if persistence is off - set: - storage: - persistence: false - - asserts: - - hasDocuments: - count: 1 - - isNull: - path: spec.volumeClaimTemplates - - - it: should not have emptyDir data volume if persistence is on - set: - storage: - persistence: true - - asserts: - - hasDocuments: - count: 1 - - notContains: - path: .spec.template.spec.volumes - content: - name: data - emptyDir: {} - - - it: should have emptyDir data volume if persistence is off - set: - storage: - persistence: false - - asserts: - - hasDocuments: - count: 1 - - contains: - path: .spec.template.spec.volumes - content: - name: data - emptyDir: {} diff --git a/charts/k0s/values.yaml b/charts/k0s/values.yaml deleted file mode 100644 index 04f4ae4f9..000000000 --- a/charts/k0s/values.yaml +++ /dev/null @@ -1,514 +0,0 @@ -# DefaultImageRegistry will be prepended to all deployed vcluster images, such as the vcluster pod, coredns etc.. Deployed -# images within the vcluster will not be rewritten. -defaultImageRegistry: "" - -# Global annotations to add to all objects -globalAnnotations: {} - -# If vCluster.Pro is enabled -pro: false - -# Defines where vCluster should search for a license secret. If you are using the vCluster.Pro control-plane, -# this is optional. vCluster by default will lookout for a secret called vc-VCLUSTER_NAME-license and if found use that. -# You can also use a secret within another namespace by using the format NAMESPACE/NAME. If the secret is in another -# namespace, please enable clusterRole.create and define an extra clusterRole.extraRules to allow vCluster to retrieve -# secrets within the cluster. -proLicenseSecret: "" - -# Embedded etcd settings -embeddedEtcd: - # If embedded etcd should be enabled, this is a PRO only feature - enabled: false - -# If true, will deploy vcluster in headless mode, which means no deployment -# or statefulset is created. -headless: false - -monitoring: - serviceMonitor: - enabled: false - -# Plugins that should get loaded. Usually you want to apply those via 'vcluster create ... -f https://.../plugin.yaml' -plugin: {} -# Manually configure a plugin called test -# test: -# image: ... -# env: ... -# rbac: -# clusterRole: -# extraRules: ... -# role: -# extraRules: ... - -# Resource syncers that should be enabled/disabled. -# Enabling syncers will impact RBAC Role and ClusterRole permissions. -# To disable a syncer set "enabled: false". -# See docs for details - https://www.vcluster.com/docs/architecture/synced-resources -sync: - services: - enabled: true - configmaps: - enabled: true - all: false - secrets: - enabled: true - all: false - endpoints: - enabled: true - pods: - enabled: true - ephemeralContainers: false - status: false - events: - enabled: true - persistentvolumeclaims: - enabled: true - ingresses: - enabled: false - ingressclasses: {} - # By default IngressClasses sync is enabled when the Ingress sync is enabled - # but it can be explicitly disabled by setting: - # enabled: false - fake-nodes: - enabled: true # will be ignored if nodes.enabled = true - fake-persistentvolumes: - enabled: true # will be ignored if persistentvolumes.enabled = true - nodes: - fakeKubeletIPs: true - enabled: false - # If nodes sync is enabled, and syncAllNodes = true, the virtual cluster - # will sync all nodes instead of only the ones where some pods are running. - syncAllNodes: false - # nodeSelector is used to limit which nodes get synced to the vcluster, - # and which nodes are used to run vcluster pods. - # A valid string representation of a label selector must be used. - nodeSelector: "" - # if true, vcluster will run with a scheduler and node changes are possible - # from within the virtual cluster. This is useful if you would like to - # taint, drain and label nodes from within the virtual cluster - enableScheduler: false - # DEPRECATED: use enable scheduler instead - # syncNodeChanges allows vcluster user edits of the nodes to be synced down to the host nodes. - # Write permissions on node resource will be given to the vcluster. - syncNodeChanges: false - persistentvolumes: - enabled: false - storageclasses: - enabled: false - # formerly named - "legacy-storageclasses" - hoststorageclasses: - enabled: false - priorityclasses: - enabled: false - networkpolicies: - enabled: false - volumesnapshots: - enabled: false - poddisruptionbudgets: - enabled: false - serviceaccounts: - enabled: false - # generic CRD configuration - generic: - config: |- - --- - -# If enabled, will fallback to host dns for resolving domains. This -# is useful if using istio or dapr in the host cluster and sidecar -# containers cannot connect to the central instance. Its also useful -# if you want to access host cluster services from within the vcluster. -fallbackHostDns: false - -# Map Services between host and virtual cluster -mapServices: - # Services that should get mapped from the - # virtual cluster to the host cluster. - # vcluster will make sure to sync the service - # ip to the host cluster automatically as soon - # as the service exists. - # For example: - # fromVirtual: - # - from: my-namespace/name - # to: host-service - fromVirtual: [] - # Same as from virtual, but instead sync services - # from the host cluster into the virtual cluster. - # If the namespace does not exist, vcluster will - # also create the namespace for the service. - fromHost: [] - -proxy: - metricsServer: - nodes: - enabled: false - pods: - enabled: false - -# Syncer configuration -syncer: - replicas: 1 - # Image to use for the syncer - # image: ghcr.io/loft-sh/vcluster - imagePullPolicy: "" - extraArgs: [] - env: [] - livenessProbe: - enabled: true - readinessProbe: - enabled: true - extraVolumeMounts: [] - resources: - limits: - ephemeral-storage: 8Gi - memory: 2Gi - requests: - ephemeral-storage: 200Mi - cpu: 10m - memory: 64Mi - kubeConfigContextName: "my-vcluster" - serviceAnnotations: {} - # Storage settings for the vcluster - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - -# Virtual Cluster (k0s) configuration -vcluster: - # Image to use for the virtual cluster - image: k0sproject/k0s:v1.29.1-k0s.0 - imagePullPolicy: "" - command: - - /binaries/k0s - baseArgs: - - controller - - --config=/tmp/k0s-config.yaml - - --data-dir=/data/k0s - # Extra arguments for k0s. - extraArgs: [] - env: [] - resources: - limits: - cpu: 100m - memory: 256Mi - requests: - cpu: 40m - memory: 64Mi - priorityClassName: "" - # clusterDomain: cluster.local - - -# Extra volumes that should be created for the StatefulSet -volumes: [] - -# Service account that should be used by the vcluster -serviceAccount: - create: true - # Optional name of the service account to use - # name: default - # Optional pull secrets - # imagePullSecrets: - # - name: my-pull-secret - -# Service account that should be used by the pods synced by vcluster -workloadServiceAccount: - # This is not supported in multi-namespace mode - annotations: {} - -# Roles & ClusterRoles for the vcluster -rbac: - clusterRole: - # You don't need to toggle this as necessary cluster roles are created based on the enabled syncers (.sync.*.enabled). - # This only makes sense to enable if you want to use the extraRules below. - create: false - # Extra Rules for the cluster role - extraRules: [] - role: - # Disable this only if you don't want vCluster to create a role. This will break most functionality if disabled. - create: true - # Extra Rules for the role - extraRules: [] - # all entries in excludedApiResources will be excluded from the Role created for vcluster - excludedApiResources: - # - pods/exec - -# If vCluster persistent volume claims should get deleted automatically. -autoDeletePersistentVolumeClaims: false - -# NodeSelector used to schedule the vcluster -nodeSelector: {} - -# Affinity to apply to the vcluster statefulset -affinity: {} - -# PriorityClassName to apply to the vcluster statefulset -priorityClassName: "" - -# Tolerations to apply to the vcluster statefulset -tolerations: [] - -# Extra Labels for the stateful set -labels: {} -podLabels: {} - -# Extra Annotations for the stateful set -annotations: {} -podAnnotations: {} - -# Service configurations -service: - type: ClusterIP - - # Optional configuration - # A list of IP addresses for which nodes in the cluster will also accept traffic for this service. - # These IPs are not managed by Kubernetes; e.g., an external load balancer. - externalIPs: [] - - # Optional configuration for LoadBalancer & NodePort service types - # Route external traffic to node-local or cluster-wide endpoints [ Local | Cluster ] - externalTrafficPolicy: "" - - # Optional configuration for LoadBalancer service type - # Specify IP of load balancer to be created - loadBalancerIP: "" - # CIDR block(s) for the service allowlist - loadBalancerSourceRanges: [] - # Set the loadBalancerClass if using an external load balancer controller - loadBalancerClass: "" - # Set loadBalancer specific annotations on the Kubernetes service - loadBalancerAnnotations: {} - -# Configure the ingress resource that allows you to access the vcluster -ingress: - # Enable ingress record generation - enabled: false - # Ingress path type - pathType: ImplementationSpecific - ingressClassName: "" - host: vcluster.local - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTPS - nginx.ingress.kubernetes.io/ssl-passthrough: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - # Ingress TLS configuration - tls: [] - # - secretName: tls-vcluster.local - # hosts: - # - vcluster.local - -# Configure SecurityContext of the containers in the VCluster pod -securityContext: - allowPrivilegeEscalation: false - # capabilities: - # drop: - # - all - # readOnlyRootFilesystem will be set to true by default at a later release - # currently leaving it undefined for backwards compatibility with older vcluster cli versions - # readOnlyRootFilesystem: true - - # To run vcluster pod as non-root uncomment runAsUser and runAsNonRoot values. - # Update the runAsUser value if your cluster has limitations on user UIDs. - # For installation on OpenShift leave the runAsUser undefined (commented out). - # runAsUser: 12345 - # runAsNonRoot: true - runAsUser: 0 - runAsGroup: 0 - -# PodSecurityContext holds pod-level security attributes and common container settings for the vCluster pod -podSecurityContext: {} - -# Custom k0s to deploy -#config: |- -# apiVersion: k0s.k0sproject.io/v1beta1 -# ... - -# Set "enable" to true when running vcluster in an OpenShift host -# This will add an extra rule to the deployed role binding in order -# to manage service endpoints -openshift: - enable: false - -# If enabled will deploy the coredns configmap -coredns: - # If CoreDns is enabled - enabled: true - # Pro only feature - integrated: false - # only used if isolation is enabled - fallback: 8.8.8.8 - plugin: - enabled: false - config: [] - # example configuration for plugin syntax, will be documented in detail - # - record: - # fqdn: google.com - # target: - # mode: url - # url: google.co.in - # - record: - # service: my-namespace/my-svc # dns-test/nginx-svc - # target: - # mode: host - # service: dns-test/nginx-svc - # - record: - # service: my-namespace-lb/my-svc-lb - # target: - # mode: host - # service: dns-test-exposed-lb/nginx-svc-exposed-lb - # - record: - # service: my-ns-external-name/my-svc-external-name - # target: - # mode: host - # service: dns-test-external-name/nginx-svc-external-name - # - record: - # service: my-ns-in-vcluster/my-svc-vcluster - # target: - # mode: vcluster # can be tested only manually for now - # vcluster: test-vcluster-ns/test-vcluster - # service: dns-test-in-vcluster-ns/test-in-vcluster-service - # - record: - # service: my-ns-in-vcluster-mns/my-svc-mns - # target: - # mode: vcluster # can be tested only manually for now - # service: dns-test-in-vcluster-mns/test-in-vcluster-svc-mns - # vcluster: test-vcluster-ns-mns/test-vcluster-mns - # - record: - # service: my-self-vc-ns/my-self-vc-svc - # target: - # mode: self - # service: dns-test/nginx-svc - replicas: 1 - # The nodeSelector example below specifices that coredns should only be scheduled to nodes with the arm64 label - # nodeSelector: - # kubernetes.io/arch: arm64 - # image: my-core-dns-image:latest - # config: |- - # .:1053 { - # ... - # CoreDNS service configurations - service: - type: ClusterIP - # Configuration for LoadBalancer service type - externalIPs: [] - externalTrafficPolicy: "" - # Extra Annotations - annotations: {} - resources: - limits: - cpu: 1000m - memory: 512Mi - requests: - # ensure that cpu/memory requests are high enough. - # for example gke wants minimum 10m/32Mi here! - cpu: 20m - memory: 64Mi -# if below option is configured, it will override the coredns manifests with the following string -# manifests: |- -# apiVersion: ... -# ... - podAnnotations: {} - podLabels: {} - -# If enabled will deploy vcluster in an isolated mode with pod security -# standards, limit ranges and resource quotas -isolation: - enabled: false - namespace: null - - podSecurityStandard: baseline - - # If enabled will add node/proxy permission to the cluster role - # in isolation mode - nodeProxyPermission: - enabled: false - - resourceQuota: - enabled: true - quota: - requests.cpu: 10 - requests.memory: 20Gi - requests.storage: "100Gi" - requests.ephemeral-storage: 60Gi - limits.cpu: 20 - limits.memory: 40Gi - limits.ephemeral-storage: 160Gi - services.nodeports: 0 - services.loadbalancers: 1 - count/endpoints: 40 - count/pods: 20 - count/services: 20 - count/secrets: 100 - count/configmaps: 100 - count/persistentvolumeclaims: 20 - scopeSelector: - matchExpressions: - scopes: - - limitRange: - enabled: true - default: - ephemeral-storage: 8Gi - memory: 512Mi - cpu: "1" - defaultRequest: - ephemeral-storage: 3Gi - memory: 128Mi - cpu: 100m - - networkPolicy: - enabled: true - outgoingConnections: - ipBlock: - cidr: 0.0.0.0/0 - except: - - 100.64.0.0/10 - - 127.0.0.0/8 - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -# manifests to setup when initializing a vcluster -init: - manifests: |- - --- - # The contents of manifests-template will be templated using helm - # this allows you to use helm values inside, e.g.: {{ .Release.Name }} - manifestsTemplate: '' - helm: [] - # - bundle: - base64-encoded .tar.gz file content (optional - overrides chart.repo) - # chart: - # name: REQUIRED - # version: REQUIRED - # repo: (optional when bundle is used) - # username: (if required for repo) - # password: (if required for repo) - # insecure: boolean (if required for repo) - # release: - # name: REQUIRED - # namespace: REQUIRED - # timeout: number - # values: |- string YAML object - # foo: bar - # valuesTemplate: |- string YAML object - # foo: {{ .Release.Name }} - -multiNamespaceMode: - enabled: false - -# list of {validating/mutating}webhooks that the syncer should proxy. -# This is a PRO only feature. -centralAdmission: - validatingWebhooks: [] - mutatingWebhooks: [] - -telemetry: - disabled: false - instanceCreator: "helm" - platformUserID: "" - platformInstanceID: "" - machineID: "" diff --git a/charts/k3s/.helmignore b/charts/k3s/.helmignore deleted file mode 100644 index b6a3eb5b3..000000000 --- a/charts/k3s/.helmignore +++ /dev/null @@ -1,24 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store - -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ - -# Common backup files -*.swp -*.bak -*.tmp -*~ - -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/charts/k3s/templates/NOTES.txt b/charts/k3s/templates/NOTES.txt deleted file mode 100644 index 6cf960d62..000000000 --- a/charts/k3s/templates/NOTES.txt +++ /dev/null @@ -1,10 +0,0 @@ -Thank you for installing vcluster. - -Your vcluster is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}. - -To connect to the vcluster, use vcluster CLI (https://www.vcluster.com/docs/getting-started/setup): - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} -- kubectl get ns - - -For more information, please take a look at the vcluster docs at https://www.vcluster.com/docs diff --git a/charts/k3s/templates/_coredns.tpl b/charts/k3s/templates/_coredns.tpl deleted file mode 100644 index c86a8bcd4..000000000 --- a/charts/k3s/templates/_coredns.tpl +++ /dev/null @@ -1,52 +0,0 @@ -{{/* - Define a common coredns config -*/}} -{{- define "vcluster.corefile" -}} -Corefile: |- - {{- if .Values.coredns.config }} -{{ .Values.coredns.config | indent 8 }} - {{- else }} - .:1053 { - errors - health - ready - rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local - kubernetes cluster.local in-addr.arpa ip6.arpa { - {{- if .Values.pro }} - {{- if .Values.coredns.integrated }} - kubeconfig /data/k3s-config/kube-config.yaml - {{- end }} - {{- end }} - pods insecure - {{- if or (.Values.fallbackHostDns) (and .Values.coredns.integrated .Values.coredns.plugin.enabled) }} - fallthrough cluster.local in-addr.arpa ip6.arpa - {{- else }} - fallthrough in-addr.arpa ip6.arpa - {{- end }} - } - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - vcluster {{ toYaml .Values.coredns.plugin.config | b64enc }} - {{- end }} - hosts /etc/NodeHosts { - ttl 60 - reload 15s - fallthrough - } - prometheus :9153 - {{- if .Values.fallbackHostDns }} - forward . {{`{{.HOST_CLUSTER_DNS}}`}} - {{- else if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} - forward . /etc/resolv.conf {{ .Values.coredns.fallback }} { - policy sequential - } - {{- else }} - forward . /etc/resolv.conf - {{- end }} - cache 30 - loop - loadbalance - } - - import /etc/coredns/custom/*.server - {{- end }} -{{- end -}} diff --git a/charts/k3s/templates/_helpers.tpl b/charts/k3s/templates/_helpers.tpl deleted file mode 100644 index 50f08d6f9..000000000 --- a/charts/k3s/templates/_helpers.tpl +++ /dev/null @@ -1,196 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "vcluster.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Whether the ingressclasses syncer should be enabled -*/}} -{{- define "vcluster.syncIngressclassesEnabled" -}} -{{- if or - (.Values.sync.ingressclasses).enabled - (and - .Values.sync.ingresses.enabled - (not .Values.sync.ingressclasses)) -}} - {{- true -}} -{{- end -}} -{{- end -}} - -{{/* -Whether to create a cluster role or not -*/}} -{{- define "vcluster.createClusterRole" -}} -{{- if or - (not (empty (include "vcluster.serviceMapping.fromHost" . ))) - (not (empty (include "vcluster.plugin.clusterRoleExtraRules" . ))) - (not (empty (include "vcluster.generic.clusterRoleExtraRules" . ))) - .Values.rbac.clusterRole.create - .Values.sync.hoststorageclasses.enabled - (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") - (include "vcluster.syncIngressclassesEnabled" . ) - .Values.pro - .Values.sync.nodes.enabled - .Values.sync.persistentvolumes.enabled - .Values.sync.storageclasses.enabled - .Values.sync.priorityclasses.enabled - .Values.sync.volumesnapshots.enabled - .Values.proxy.metricsServer.nodes.enabled - .Values.multiNamespaceMode.enabled - .Values.coredns.plugin.enabled -}} -{{- true -}} -{{- end -}} -{{- end -}} - -{{- define "vcluster.clusterRoleName" -}} -{{- printf "vc-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "vcluster.clusterRoleNameMultinamespace" -}} -{{- printf "vc-mn-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Syncer flags for enabling/disabling controllers -Prints only the flags that modify the defaults: -- when default controller has enabled: false => `- "--sync=-controller` -- when non-default controller has enabled: true => `- "--sync=controller` -*/}} -{{- define "vcluster.syncer.syncArgs" -}} -{{- $defaultEnabled := list "services" "configmaps" "secrets" "endpoints" "pods" "events" "persistentvolumeclaims" "fake-nodes" "fake-persistentvolumes" -}} -{{- if and (hasKey .Values.sync.nodes "enableScheduler") .Values.sync.nodes.enableScheduler -}} - {{- $defaultEnabled = concat $defaultEnabled (list "csinodes" "csidrivers" "csistoragecapacities" ) -}} -{{- end -}} -{{- range $key, $val := .Values.sync }} -{{- if and (has $key $defaultEnabled) (not $val.enabled) }} -- --sync=-{{ $key }} -{{- else if and (not (has $key $defaultEnabled)) ($val.enabled)}} -{{- if eq $key "legacy-storageclasses" }} -- --sync=hoststorageclasses -{{- else }} -- --sync={{ $key }} -{{- end -}} -{{- end -}} -{{- end -}} -{{- if not (include "vcluster.syncIngressclassesEnabled" . ) }} -- --sync=-ingressclasses -{{- end -}} -{{- end -}} - -{{/* - Cluster role rules defined by plugins -*/}} -{{- define "vcluster.plugin.clusterRoleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.clusterRole }} -{{- if $container.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.clusterRoleExtraRules" -}} -{{- if .Values.sync.generic.clusterRole }} -{{- if .Values.sync.generic.clusterRole.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined on global level -*/}} -{{- define "vcluster.rbac.clusterRoleExtraRules" -}} -{{- if .Values.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - - -{{/* - Role rules defined on global level -*/}} -{{- define "vcluster.rbac.roleExtraRules" -}} -{{- if .Values.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined by plugins -*/}} -{{- define "vcluster.plugin.roleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.role }} -{{- if $container.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.roleExtraRules" -}} -{{- if .Values.sync.generic.role }} -{{- if .Values.sync.generic.role.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Virtual cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromVirtual" -}} -{{- range $key, $value := .Values.mapServices.fromVirtual }} -- '--map-virtual-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} - -{{/* - Host cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromHost" -}} -{{- range $key, $value := .Values.mapServices.fromHost }} -- '--map-host-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} diff --git a/charts/k3s/templates/_migrate.tpl b/charts/k3s/templates/_migrate.tpl deleted file mode 100644 index 34bef9a19..000000000 --- a/charts/k3s/templates/_migrate.tpl +++ /dev/null @@ -1,6 +0,0 @@ -{{/* - handles both replicas and syncer.replicas -*/}} -{{- define "vcluster.replicas" -}} -{{ if .Values.replicas }}{{ .Values.replicas }}{{ else }}{{ .Values.syncer.replicas }}{{ end }} -{{- end }} diff --git a/charts/k3s/templates/_plugin.tpl b/charts/k3s/templates/_plugin.tpl deleted file mode 100644 index b1ecd2fa2..000000000 --- a/charts/k3s/templates/_plugin.tpl +++ /dev/null @@ -1,188 +0,0 @@ -{{/* - Plugin config definition -*/}} -{{- define "vcluster.plugins.config" -}} -{{- $pluginFound := false -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} -{{- $pluginFound = true -}} -{{- end }} -{{- if $pluginFound }} -- name: PLUGIN_CONFIG - value: |- -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} - {{ $key }}: {{ toYaml $container.config | nindent 6 }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume mount definition -*/}} -{{- define "vcluster.plugins.volumeMounts" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- mountPath: /plugins - name: plugins -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume definition -*/}} -{{- define "vcluster.plugins.volumes" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- name: plugins - emptyDir: {} -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin init container definition -*/}} -{{- define "vcluster.plugins.initContainers" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if or $container.command $container.args }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- else }} - command: ["sh"] - args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] - {{- end }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - {{- if $container.volumeMounts }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- else }} - volumeMounts: - - mountPath: /plugins - name: plugins - {{- end }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* - Extra Syncer Args for the legacy Plugins -*/}} -{{- define "vcluster.legacyPlugins.args" -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{- continue }} -{{- end }} -{{- if not $container.optional }} -- --plugins={{ $key }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Sidecar container definition for the legacy syncer parts -*/}} -{{- define "vcluster.legacyPlugins.containers" -}} -{{- $counter := -1 -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{ continue }} -{{- end }} -{{- $counter = add1 $counter }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if $container.workingDir }} - workingDir: {{ $container.workingDir }} - {{- end }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- if $container.terminationMessagePath }} - terminationMessagePath: {{ $container.terminationMessagePath }} - {{- end }} - {{- if $container.terminationMessagePolicy }} - terminationMessagePolicy: {{ $container.terminationMessagePolicy }} - {{- end }} - env: - - name: VCLUSTER_PLUGIN_ADDRESS - value: "localhost:{{ add 14000 $counter }}" - - name: VCLUSTER_PLUGIN_NAME - value: "{{ $key }}" - {{- if $container.env }} -{{ toYaml $container.env | indent 4 }} - {{- end }} - envFrom: -{{ toYaml $container.envFrom | indent 4 }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - lifecycle: -{{ toYaml $container.lifecycle | indent 4 }} - livenessProbe: -{{ toYaml $container.livenessProbe | indent 4 }} - readinessProbe: -{{ toYaml $container.readinessProbe | indent 4 }} - startupProbe: -{{ toYaml $container.startupProbe | indent 4 }} - volumeDevices: -{{ toYaml $container.volumeDevices | indent 4 }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} - {{- end }} -{{- end }} - - diff --git a/charts/k3s/templates/_storage.tpl b/charts/k3s/templates/_storage.tpl deleted file mode 100644 index b10675ab6..000000000 --- a/charts/k3s/templates/_storage.tpl +++ /dev/null @@ -1,20 +0,0 @@ -{{/* - storage size -*/}} -{{- define "vcluster.storage.size" -}} -{{if .Values.storage }}{{ .Values.storage.size }}{{ else }}{{ .Values.syncer.storage.size }}{{ end }} -{{- end -}} - -{{/* - storage persistence -*/}} -{{- define "vcluster.storage.persistence" -}} -{{if .Values.storage }}{{ .Values.storage.persistence }}{{ else }}{{ .Values.syncer.storage.persistence }}{{ end }} -{{- end -}} - -{{/* - storage classname -*/}} -{{- define "vcluster.storage.className" -}} -{{if .Values.storage }}{{ .Values.storage.className }}{{ else }}{{ .Values.syncer.storage.className }}{{ end }} -{{- end -}} diff --git a/charts/k3s/templates/coredns.yaml b/charts/k3s/templates/coredns.yaml deleted file mode 100644 index cc25dd17f..000000000 --- a/charts/k3s/templates/coredns.yaml +++ /dev/null @@ -1,231 +0,0 @@ -{{- if not .Values.headless }} -{{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-coredns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{- if .Values.coredns.manifests }} - coredns.yaml: |- -{{ .Values.coredns.manifests | indent 4 }} -{{- else }} - coredns.yaml: |- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: coredns - namespace: kube-system - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - rules: - - apiGroups: - - "" - resources: - - endpoints - - services - - pods - - namespaces - verbs: - - list - - watch - - apiGroups: - - discovery.k8s.io - resources: - - endpointslices - verbs: - - list - - watch - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - annotations: - rbac.authorization.kubernetes.io/autoupdate: "true" - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:coredns - subjects: - - kind: ServiceAccount - name: coredns - namespace: kube-system - --- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: -{{ include "vcluster.corefile" . | indent 6 }} - NodeHosts: "" - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: coredns - namespace: kube-system - labels: - k8s-app: kube-dns - kubernetes.io/name: "CoreDNS" - spec: - replicas: {{ .Values.coredns.replicas }} - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - selector: - matchLabels: - k8s-app: kube-dns - template: - metadata: - {{- if .Values.coredns.podAnnotations }} - annotations: -{{ toYaml .Values.coredns.podAnnotations | indent 12 }} - {{- end }} - labels: - k8s-app: kube-dns - {{- range $k, $v := .Values.coredns.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - priorityClassName: "system-cluster-critical" - serviceAccountName: coredns - nodeSelector: - kubernetes.io/os: linux - {{- if .Values.coredns.nodeSelector }} -{{ toYaml .Values.coredns.nodeSelector | indent 12 }} - {{- end }} - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: DoNotSchedule - labelSelector: - matchLabels: - k8s-app: kube-dns - {{- if .Values.isolation.enabled }} - securityContext: - seccompProfile: - type: RuntimeDefault - {{- end }} - containers: - - name: coredns - {{- if .Values.coredns.image }} - image: {{ .Values.defaultImageRegistry }}{{ .Values.coredns.image }} - {{- else }} - image: {{`{{.IMAGE}}`}} - {{- end }} - imagePullPolicy: IfNotPresent - resources: -{{ toYaml .Values.coredns.resources | indent 16}} - args: [ "-conf", "/etc/coredns/Corefile" ] - volumeMounts: - - name: config-volume - mountPath: /etc/coredns - readOnly: true - - name: custom-config-volume - mountPath: /etc/coredns/custom - readOnly: true - securityContext: - runAsNonRoot: true - runAsUser: {{`{{.RUN_AS_USER}}`}} - runAsGroup: {{`{{.RUN_AS_GROUP}}`}} - allowPrivilegeEscalation: false - capabilities: - add: - - NET_BIND_SERVICE - drop: - - ALL - readOnlyRootFilesystem: true - livenessProbe: - httpGet: - path: /health - port: 8080 - scheme: HTTP - initialDelaySeconds: 60 - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /ready - port: 8181 - scheme: HTTP - initialDelaySeconds: 0 - periodSeconds: 2 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - dnsPolicy: Default - volumes: - - name: config-volume - configMap: - name: coredns - items: - - key: Corefile - path: Corefile - - key: NodeHosts - path: NodeHosts - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - selector: - k8s-app: kube-dns - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} -{{- end }} diff --git a/charts/k3s/templates/ingress.yaml b/charts/k3s/templates/ingress.yaml deleted file mode 100644 index d629f096f..000000000 --- a/charts/k3s/templates/ingress.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - {{- $annotations := merge .Values.ingress.annotations .Values.globalAnnotations }} - {{- if .Values.ingress.tls }} - {{- $annotations = omit $annotations "nginx.ingress.kubernetes.io/ssl-passthrough" }} - {{- end }} - {{- if $annotations }} - annotations: - {{- toYaml $annotations | nindent 4 }} - {{- end }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - {{- if .Values.ingress.ingressClassName }} - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} - {{- end }} - rules: - - host: {{ .Values.ingress.host | quote }} - http: - paths: - - backend: - service: - name: {{ .Release.Name }} - port: - name: https - path: / - pathType: {{ .Values.ingress.pathType }} - {{- with .Values.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end -}} -{{- end }} diff --git a/charts/k3s/templates/init-configmap.yaml b/charts/k3s/templates/init-configmap.yaml deleted file mode 100644 index 34498fd14..000000000 --- a/charts/k3s/templates/init-configmap.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.init.manifests }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-init-manifests - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: - manifests: |- - {{ .Values.init.manifests | nindent 4 | trim }} - {{ tpl .Values.init.manifestsTemplate $ | nindent 4 | trim }} - {{- if .Values.init.helm }} - charts: |- - {{- range .Values.init.helm }} - {{- /* only render this chart entry if either of chart or bundle is defined */}} - {{- if .chart }} - - name: {{ .chart.name }} - repo: {{ .chart.repo }} - version: {{ .chart.version }} - {{- if .chart.username }} - username: {{ .chart.username }} - {{- end }} - {{- if .chart.password }} - password: {{ .chart.password }} - {{- end }} - {{- if .insecure }} - insecure: true - {{- end}} - {{- end }} - {{- if .bundle }} - bundle: {{ .bundle }} - {{- end }} - {{- if or .chart .bundle }} - {{- if or .values .valuesTemplate }} - values: |- - {{ (.values | default "") | nindent 8 | trim }} - {{ tpl (.valuesTemplate | default "") $ | nindent 8 | trim }} - {{- end}} - {{- if .release }} - timeout: {{ .timeout | default "120s" | quote }} - releaseName: {{ .release.name }} - releaseNamespace: {{ .release.namespace }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k3s/templates/integrated-coredns.yaml b/charts/k3s/templates/integrated-coredns.yaml deleted file mode 100644 index 16907cba0..000000000 --- a/charts/k3s/templates/integrated-coredns.yaml +++ /dev/null @@ -1,64 +0,0 @@ -{{- if .Values.pro }} -{{- if .Values.coredns.integrated }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-dns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{ include "vcluster.corefile" . | indent 2 }} - coredns.yaml: |- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: - NodeHosts: "" - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} diff --git a/charts/k3s/templates/limitrange.yaml b/charts/k3s/templates/limitrange.yaml deleted file mode 100644 index ed219d063..000000000 --- a/charts/k3s/templates/limitrange.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.limitRange.enabled }} -apiVersion: v1 -kind: LimitRange -metadata: - name: {{ .Release.Name }}-limit-range - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - limits: - - default: - {{- range $key, $val := .Values.isolation.limitRange.default }} - {{ $key }}: {{ $val | quote }} - {{- end }} - defaultRequest: - {{- range $key, $val := .Values.isolation.limitRange.defaultRequest }} - {{ $key }}: {{ $val | quote }} - {{- end }} - type: Container -{{- end }} diff --git a/charts/k3s/templates/networkpolicy.yaml b/charts/k3s/templates/networkpolicy.yaml deleted file mode 100644 index e40d63356..000000000 --- a/charts/k3s/templates/networkpolicy.yaml +++ /dev/null @@ -1,74 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-workloads - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - egress: - # Allows outgoing connections to the vcluster control plane - - ports: - - port: 443 - - port: 8443 - to: - - podSelector: - matchLabels: - release: {{ .Release.Name }} - # Allows outgoing connections to DNS server - - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP - # Allows outgoing connections to the internet or - # other vcluster workloads - - to: - - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - - ipBlock: - cidr: {{ .Values.isolation.networkPolicy.outgoingConnections.ipBlock.cidr }} - except: - {{- range .Values.isolation.networkPolicy.outgoingConnections.ipBlock.except }} - - {{ . }} - {{- end }} - policyTypes: - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-control-plane - namespace: {{ .Release.Namespace }} -spec: - podSelector: - matchLabels: - release: {{ .Release.Name }} - egress: - # Allows outgoing connections to all pods with - # port 443, 8443 or 6443. This is needed for host Kubernetes - # access - - ports: - - port: 443 - - port: 8443 - - port: 6443 - # Allows outgoing connections to all vcluster workloads - # or kube system dns server - - to: - - podSelector: {} - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: 'kube-system' - podSelector: - matchLabels: - k8s-app: kube-dns - policyTypes: - - Egress -{{- end }} diff --git a/charts/k3s/templates/rbac/clusterrolebinding.yaml b/charts/k3s/templates/rbac/clusterrolebinding.yaml deleted file mode 100644 index 0a5645d28..000000000 --- a/charts/k3s/templates/rbac/clusterrolebinding.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: - kind: ClusterRole - name: {{ template "vcluster.clusterRoleName" . }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/k3s/templates/rbac/role.yaml b/charts/k3s/templates/rbac/role.yaml deleted file mode 100644 index de05bbb62..000000000 --- a/charts/k3s/templates/rbac/role.yaml +++ /dev/null @@ -1,104 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRole -{{- else -}} -kind: Role -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} -{{- end }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: - {{- if .Values.pro }} - - apiGroups: [""] - {{- $resources := list "configmaps" "secrets" "services" "pods" "pods/attach" "pods/portforward" "pods/exec" "persistentvolumeclaims" }} - {{- range $excluded := .Values.rbac.role.excludedApiResources }} - {{- $resources = without $resources $excluded }} - {{- end}} - resources: {{ $resources | toJson }} - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- else }} - - apiGroups: [""] - resources: ["configmaps", "secrets", "services", "pods", "pods/attach", "pods/portforward", "pods/exec", "persistentvolumeclaims"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.status }} - - apiGroups: [""] - resources: ["pods/status"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.ephemeralContainers }} - - apiGroups: [""] - resources: ["pods/ephemeralcontainers"] - verbs: ["patch", "update"] - {{- end }} - {{- if or .Values.sync.endpoints.enabled .Values.headless }} - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["create", "delete", "patch", "update"] - {{- end }} - {{- if gt (int .Values.syncer.replicas) 1 }} - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: [""] - resources: ["endpoints", "events", "pods/log"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.ingresses.enabled}} - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: ["apps"] - resources: ["statefulsets", "replicasets", "deployments"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.networkpolicies.enabled }} - - apiGroups: ["networking.k8s.io"] - resources: ["networkpolicies"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.serviceaccounts.enabled }} - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.poddisruptionbudgets.enabled }} - - apiGroups: ["policy"] - resources: ["poddisruptionbudgets"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.openshift.enable }} - {{- if .Values.sync.endpoints.enabled }} - - apiGroups: [""] - resources: ["endpoints/restricted"] - verbs: ["create"] - {{- end }} - {{- end }} - {{- if .Values.proxy.metricsServer.pods.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["pods"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.roleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.roleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.roleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/k3s/templates/resourcequota.yaml b/charts/k3s/templates/resourcequota.yaml deleted file mode 100644 index b19d89fff..000000000 --- a/charts/k3s/templates/resourcequota.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.resourceQuota.enabled }} -apiVersion: v1 -kind: ResourceQuota -metadata: - name: {{ .Release.Name }}-quota - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - hard: - {{- range $key, $val := .Values.isolation.resourceQuota.quota }} - {{ $key }}: {{ $val | quote }} - {{- end }} - - {{- if .Values.isolation.resourceQuota.scopeSelector.matchExpressions }} - scopeSelector: - matchExpressions: - {{- toYaml .Values.isolation.resourceQuota.scopeSelector.matchExpressions | nindent 4 }} - {{- end}} - - {{- if .Values.isolation.resourceQuota.scopes }} - scopes: - {{- toYaml .Values.isolation.resourceQuota.scopes | nindent 4 }} - {{- end}} - -{{- end }} diff --git a/charts/k3s/templates/service-monitor.yaml b/charts/k3s/templates/service-monitor.yaml deleted file mode 100644 index 683506ab5..000000000 --- a/charts/k3s/templates/service-monitor.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.monitoring.serviceMonitor.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - selector: - matchLabels: - app: vcluster - release: "{{ .Release.Name }}" - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - heritage: "{{ .Release.Service }}" - endpoints: - - interval: 30s - port: https - path: /metrics - scheme: https - tlsConfig: - ca: - secret: - name: vc-{{ .Release.Name }} - key: certificate-authority - cert: - secret: - name: vc-{{ .Release.Name }} - key: client-certificate - keySecret: - name: vc-{{ .Release.Name }} - key: client-key -{{- end }} diff --git a/charts/k3s/templates/service.yaml b/charts/k3s/templates/service.yaml deleted file mode 100644 index b575a9fe1..000000000 --- a/charts/k3s/templates/service.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: {{ if eq .Values.service.type "LoadBalancer" -}}ClusterIP{{- else }} {{- .Values.service.type }} {{- end }} - ports: - - name: https - port: 443 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.httpsNodePort }} - protocol: TCP - - name: kubelet - port: 10250 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.kubeletNodePort }} - protocol: TCP - {{- if .Values.service.externalIPs }} - externalIPs: - {{- range $f := .Values.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- if eq .Values.service.type "NodePort" }} - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} ---- -{{ if eq .Values.service.type "LoadBalancer" }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-lb - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.service.loadBalancerAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: LoadBalancer - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} - {{- if .Values.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.service.loadBalancerIP }} - {{- end }} - {{- if .Values.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.service.loadBalancerClass }} - {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: - {{- range $f := .Values.service.loadBalancerSourceRanges }} - - "{{ $f }}" - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k3s/templates/serviceaccount.yaml b/charts/k3s/templates/serviceaccount.yaml deleted file mode 100644 index 504998037..000000000 --- a/charts/k3s/templates/serviceaccount.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} -{{- end }} diff --git a/charts/k3s/templates/statefulset-service.yaml b/charts/k3s/templates/statefulset-service.yaml deleted file mode 100644 index 7523bf36e..000000000 --- a/charts/k3s/templates/statefulset-service.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-headless - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "vcluster.fullname" . }} - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - publishNotReadyAddresses: true - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.embeddedEtcd.enabled }} - - name: etcd - port: 2379 - targetPort: 2379 - protocol: TCP - - name: peer - port: 2380 - targetPort: 2380 - protocol: TCP - {{- end }} - clusterIP: None - selector: - app: vcluster - release: "{{ .Release.Name }}" -{{- end }} diff --git a/charts/k3s/templates/syncer.yaml b/charts/k3s/templates/syncer.yaml deleted file mode 100644 index a83a8e250..000000000 --- a/charts/k3s/templates/syncer.yaml +++ /dev/null @@ -1,431 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -{{- if .Values.labels }} -{{ toYaml .Values.labels | indent 4 }} -{{- end }} -{{- $annotations := merge .Values.annotations .Values.globalAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - serviceName: {{ .Release.Name }}-headless - {{- if .Values.autoDeletePersistentVolumeClaims }} - {{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} - persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete - {{- end }} - {{- end }} - replicas: {{include "vcluster.replicas" .}} - selector: - matchLabels: - app: vcluster - release: {{ .Release.Name }} - {{- if (hasKey .Values "volumeClaimTemplates") }} - volumeClaimTemplates: -{{ toYaml .Values.volumeClaimTemplates | indent 4 }} - {{- else if (eq ( include "vcluster.storage.persistence" . ) "true" ) }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - {{- if ( include "vcluster.storage.className" . ) }} - storageClassName: {{ include "vcluster.storage.className" . }} - {{- end }} - resources: - requests: - storage: {{ include "vcluster.storage.size" . }} - {{- end }} - template: - metadata: - {{- if .Values.podAnnotations }} - annotations: -{{ toYaml .Values.podAnnotations | indent 8 }} - {{- end }} - labels: - app: vcluster - release: {{ .Release.Name }} - {{- range $k, $v := .Values.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - terminationGracePeriodSeconds: 10 - nodeSelector: -{{ toYaml .Values.nodeSelector | indent 8 }} - {{- if .Values.affinity }} - affinity: -{{ toYaml .Values.affinity | indent 8 }} - {{- else if (gt (int ( include "vcluster.replicas" . )) 1) }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - # if possible avoid scheduling more than one pod on one node - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: "kubernetes.io/hostname" - # if possible avoid scheduling pod onto node that is in the same zone as one or more vcluster pods are running - - weight: 50 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: topology.kubernetes.io/zone - {{- end }} - tolerations: -{{ toYaml .Values.tolerations | indent 8 }} - {{- if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} - {{- else }} - serviceAccountName: vc-{{ .Release.Name }} - {{- end }} - volumes: - {{- include "vcluster.plugins.volumes" . | indent 8 }} - - name: helm-cache - emptyDir: {} - - name: binaries - emptyDir: {} - - name: tmp - emptyDir: {} - - name: config - emptyDir: {} - {{- if .Values.syncer.volumes }} -{{ toYaml .Values.syncer.volumes | indent 8 }} - {{- end }} - {{- if .Values.volumes }} -{{ toYaml .Values.volumes | indent 8 }} - {{- end }} - {{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} - - name: coredns - configMap: - name: {{ .Release.Name }}-coredns - {{- else if .Values.coredns.integrated }} - - name: coredns - configMap: - name: {{ .Release.Name }}-dns - {{- end }} - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - {{- if not (eq ( include "vcluster.storage.persistence" . ) "true" ) }} - - name: data - emptyDir: {} - {{- end }} - {{- if and .Values.noopSyncer.enabled .Values.noopSyncer.secret }} - - name: noopsyncer-cluster-certs - secret: - secretName: {{ .Values.noopSyncer.secret }} - optional: true - {{- end }} - {{- if .Values.priorityClassName }} - priorityClassName: {{ .Values.priorityClassName }} - {{- end }} - {{- if or .Values.podSecurityContext .Values.fsGroup }} - {{- $fsContext := dict "fsGroup" .Values.fsGroup }} - {{- with merge .Values.podSecurityContext $fsContext }} - securityContext: -{{ toYaml . | indent 8 }} - {{- end }} - {{- end }} - {{- if and (not .Values.vcluster.disabled) (not .Values.noopSyncer.enabled) }} - initContainers: - {{- include "vcluster.plugins.initContainers" . | indent 6 }} - - image: {{ .Values.defaultImageRegistry }}{{ .Values.vcluster.image }} - name: vcluster - # k3s has a problem running as pid 1 and disabled agents on cgroupv2 - # nodes as it will try to evacuate the cgroups there. Starting k3s - # through a shell makes it non pid 1 and prevents this from happening - command: - - /bin/sh - args: - - -c - - "cp /bin/k3s /binaries/k3s" - {{- if .Values.vcluster.imagePullPolicy }} - imagePullPolicy: {{ .Values.vcluster.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.securityContext | indent 10 }} - volumeMounts: - - name: binaries - mountPath: /binaries - resources: -{{ toYaml .Values.vcluster.resources | indent 10 }} - {{- end }} - containers: - {{- if not .Values.syncer.disabled }} - - name: syncer - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - {{- if .Values.syncer.workingDir }} - workingDir: {{ .Values.syncer.workingDir }} - {{- end }} - {{- if .Values.syncer.command }} - command: - {{- range $f := .Values.syncer.command }} - - {{ $f | quote }} - {{- end }} - {{- end }} - args: - - --name={{ .Release.Name }} - - --kube-config=/data/k3s-config/kube-config.yaml - - --service-account=vc-workload-{{ .Release.Name }} - {{- include "vcluster.legacyPlugins.args" . | indent 10 }} - {{- if .Values.pro }} - {{- if .Values.proLicenseSecret }} - - --pro-license-secret={{ .Values.proLicenseSecret }} - {{- end }} - {{- if .Values.embeddedEtcd.enabled }} - - --etcd-embedded - - --etcd-replicas={{ include "vcluster.replicas" . }} - {{- end }} - {{- end }} - {{- if .Values.sync.nodes.enableScheduler }} - - --enable-scheduler - {{- end }} - {{- if .Values.defaultImageRegistry }} - - --default-image-registry={{ .Values.defaultImageRegistry }} - {{- end }} - {{- if .Values.syncer.kubeConfigContextName }} - - --kube-config-context-name={{ .Values.syncer.kubeConfigContextName }} - {{- end }} - {{- if (gt (int ( include "vcluster.replicas" . )) 1) }} - - --leader-elect=true - {{- else }} - - --leader-elect=false - {{- end }} - {{- if .Values.ingress.enabled }} - - --tls-san={{ .Values.ingress.host }} - {{- end }} - {{- if .Values.isolation.enabled }} - - --enforce-pod-security-standard={{ .Values.isolation.podSecurityStandard }} - {{- end}} - {{- include "vcluster.syncer.syncArgs" . | indent 10 }} - {{- if .Values.sync.nodes.syncAllNodes }} - - --sync-all-nodes - {{- end }} - {{- if .Values.sync.nodes.nodeSelector }} - - --node-selector={{ .Values.sync.nodes.nodeSelector }} - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - --multi-namespace-mode=true - {{- end }} - {{- if .Values.sync.configmaps.all }} - - --sync-all-configmaps=true - {{- end }} - {{- if .Values.sync.secrets.all }} - - --sync-all-secrets=true - {{- end }} - {{- if not .Values.sync.nodes.fakeKubeletIPs }} - - --fake-kubelet-ips=false - {{- end }} - {{- if or .Values.proxy.metricsServer.nodes.enabled .Values.proxy.metricsServer.pods.enabled }} - - --proxy-metrics-server=true - {{- end }} - {{- if .Values.coredns.integrated }} - - --integrated-coredns=true - {{- end }} - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - - --use-coredns-plugin=true - {{- end }} - {{- if .Values.noopSyncer.enabled }} - - --noop-syncer=true - {{- if .Values.noopSyncer.synck8sService }} - - --sync-k8s-service=true - {{- end }} - {{- end }} - {{- if .Values.centralAdmission.validatingWebhooks }} - {{- range .Values.centralAdmission.validatingWebhooks }} - - --enforce-validating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- if .Values.centralAdmission.mutatingWebhooks }} - {{- range .Values.centralAdmission.mutatingWebhooks }} - - --enforce-mutating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- range $f := .Values.syncer.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- include "vcluster.serviceMapping.fromHost" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromVirtual" . | indent 10 }} - {{- if .Values.syncer.livenessProbe }} - {{- if .Values.syncer.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: /healthz - port: 8443 - scheme: HTTPS - failureThreshold: 60 - initialDelaySeconds: 60 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.readinessProbe }} - {{- if .Values.syncer.readinessProbe.enabled }} - startupProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 300 - periodSeconds: 6 - readinessProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 60 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.securityContext | indent 10 }} - env: - {{- include "vcluster.plugins.config" . | indent 10 }} - - name: VCLUSTER_DISTRO - value: k3s - {{- if .Values.vcluster.env }} -{{ toYaml .Values.vcluster.env | indent 10 }} - {{- end }} - {{- if .Values.syncer.env }} -{{ toYaml .Values.syncer.env | indent 10 }} - {{- end }} - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: VCLUSTER_COMMAND - value: |- - command: - {{ range $f := .Values.vcluster.command }} - - {{ $f }} - {{- end }} - args: - {{- range $f := .Values.vcluster.baseArgs }} - - {{ $f }} - {{- end }} - {{- if not .Values.sync.nodes.enableScheduler }} - - --disable-scheduler - - --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl - - --kube-apiserver-arg=endpoint-reconciler-type=none - {{- else }} - - --kube-controller-manager-arg=controllers=*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl - - --kube-apiserver-arg=endpoint-reconciler-type=none - - --kube-controller-manager-arg=node-monitor-grace-period=1h - - --kube-controller-manager-arg=node-monitor-period=1h - {{- end }} - {{- if .Values.pro }} - {{- if .Values.embeddedEtcd.enabled }} - - --datastore-endpoint=https://localhost:2379 - - --datastore-cafile=/data/pki/etcd/ca.crt - - --datastore-certfile=/data/pki/apiserver-etcd-client.crt - - --datastore-keyfile=/data/pki/apiserver-etcd-client.key - {{- end }} - {{- end }} - {{- range $f := .Values.vcluster.extraArgs }} - - {{ $f }} - {{- end }} - {{- if eq ( ( include "vcluster.replicas" . ) | toString | atoi) 1 }} - - name: VCLUSTER_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - {{- end }} - {{- if .Values.sync.generic.config }} - - name: CONFIG - value: |- - {{- .Values.sync.generic.config | nindent 14 }} - {{- end }} - - name: VCLUSTER_TELEMETRY_CONFIG - value: {{ .Values.telemetry | toJson | quote }} - volumeMounts: - {{- include "vcluster.plugins.volumeMounts" . | indent 10 }} - - name: binaries - mountPath: /binaries - - name: helm-cache - mountPath: /.cache/helm - - name: config - mountPath: /etc/rancher - - name: tmp - mountPath: /tmp - {{- if or .Values.coredns.enabled .Values.coredns.integrated }} - - name: coredns - mountPath: /manifests/coredns - readOnly: true - - name: custom-config-volume - mountPath: /etc/coredns/custom - readOnly: true - {{- end }} - {{- if and .Values.noopSyncer.enabled .Values.noopSyncer.secret }} - - name: noopsyncer-cluster-certs - mountPath: /data/server/tls/request-header-ca.crt - subPath: requestHeaderCaCert - - name: noopsyncer-cluster-certs - mountPath: /data/server/tls/client-ca.crt - subPath: clientCaCert - - name: noopsyncer-cluster-certs - mountPath: /data/server/tls/server-ca.crt - subPath: serverCaCert - - name: noopsyncer-cluster-certs - mountPath: /data/server/tls/server-ca.key - subPath: serverCaKey - - name: noopsyncer-cluster-certs - mountPath: /data/server/cred/admin.kubeconfig - subPath: kubeConfig - {{- end }} -{{ toYaml .Values.syncer.volumeMounts | indent 10 }} - {{- if .Values.syncer.extraVolumeMounts }} -{{ toYaml .Values.syncer.extraVolumeMounts | indent 10 }} - {{- end }} - {{- if .Values.vcluster.volumeMounts }} -{{ toYaml .Values.vcluster.volumeMounts | indent 10 }} - {{- end }} - {{- if .Values.vcluster.extraVolumeMounts }} -{{ toYaml .Values.vcluster.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} -{{- include "vcluster.legacyPlugins.containers" . | indent 6 }} -{{- end }} diff --git a/charts/k3s/templates/token-secret.yaml b/charts/k3s/templates/token-secret.yaml deleted file mode 100644 index a87189fd8..000000000 --- a/charts/k3s/templates/token-secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -{{- if .Values.k3sToken }} -apiVersion: v1 -kind: Secret -metadata: - name: "vc-k3s-{{ .Release.Name }}" - namespace: {{ .Release.Namespace }} -type: Opaque -data: - token: {{ .Values.k3sToken | b64enc | quote }} -{{- end }} diff --git a/charts/k3s/templates/workloadserviceaccount.yaml b/charts/k3s/templates/workloadserviceaccount.yaml deleted file mode 100644 index 8d05fc1e8..000000000 --- a/charts/k3s/templates/workloadserviceaccount.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-workload-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.workloadServiceAccount.annotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} diff --git a/charts/k3s/tests/clusterrole_test.yaml b/charts/k3s/tests/clusterrole_test.yaml deleted file mode 100644 index 3385c786c..000000000 --- a/charts/k3s/tests/clusterrole_test.yaml +++ /dev/null @@ -1,41 +0,0 @@ -suite: ClusterRole -templates: - - rbac/clusterrole.yaml - -tests: - - it: should create clusterrole - set: - rbac: - clusterRole: - create: true - asserts: - - hasDocuments: - count: 1 - - it: should not create clusterrole - set: - rbac: - clusterRole: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - clusterRole: - create: true - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k3s/tests/legacy-plugins_test.yaml b/charts/k3s/tests/legacy-plugins_test.yaml deleted file mode 100644 index 6977c7562..000000000 --- a/charts/k3s/tests/legacy-plugins_test.yaml +++ /dev/null @@ -1,77 +0,0 @@ -suite: Legacy Plugins -templates: - - syncer.yaml - -tests: - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - bootstrap-with-deployment2: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment2" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[2].name - value: bootstrap-with-deployment2 - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[2].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - equal: - path: spec.template.spec.containers[2].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14001 - - - it: should check no legacy plugin rendering - asserts: - - hasDocuments: - count: 1 - - lengthEqual: - path: spec.template.spec.containers - count: 1 diff --git a/charts/k3s/tests/plugins_test.yaml b/charts/k3s/tests/plugins_test.yaml deleted file mode 100644 index 2ce3149dc..000000000 --- a/charts/k3s/tests/plugins_test.yaml +++ /dev/null @@ -1,100 +0,0 @@ -suite: Plugins -templates: - - syncer.yaml - -tests: - - it: should check plugin config rendering - set: - plugin: - plugin1: - version: v2 - config: - myConfig: true - plugin2: - version: v2 - image: test - plugin3: - version: v2 - image: test123 - config: - myOtherConfig: - - test123 - - test456 - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.containers[0].env[0].name - value: PLUGIN_CONFIG - - equal: - path: spec.template.spec.containers[0].env[0].value - value: |- - plugin1: - myConfig: true - plugin3: - myOtherConfig: - - test123 - - test456 - - - it: should check plugin rendering - set: - plugin: - bootstrap-with-deployment: - version: v2 - image: test - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.volumes[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins diff --git a/charts/k3s/tests/role_test.yaml b/charts/k3s/tests/role_test.yaml deleted file mode 100644 index 5eeedba74..000000000 --- a/charts/k3s/tests/role_test.yaml +++ /dev/null @@ -1,32 +0,0 @@ -suite: Role -templates: - - rbac/role.yaml - -tests: - - it: should not create role - set: - rbac: - role: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - role: - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k3s/tests/syncer_test.yaml b/charts/k3s/tests/syncer_test.yaml deleted file mode 100644 index 685792854..000000000 --- a/charts/k3s/tests/syncer_test.yaml +++ /dev/null @@ -1,65 +0,0 @@ -suite: Syncer -templates: - - syncer.yaml - -tests: - - it: should pass pro license secret as a flag - set: - pro: true - proLicenseSecret: "my-test-secret" - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--pro-license-secret=my-test-secret" - count: 1 - - it: should have pvc if persistence is on - set: - storage: - persistence: true - - asserts: - - hasDocuments: - count: 1 - - isNotEmpty: - path: spec.volumeClaimTemplates - - - it: should not have pvc if persistence is off - set: - storage: - persistence: false - - asserts: - - hasDocuments: - count: 1 - - isNull: - path: spec.volumeClaimTemplates - - - it: should not have emptyDir data volume if persistence is on - set: - storage: - persistence: true - - asserts: - - hasDocuments: - count: 1 - - notContains: - path: .spec.template.spec.volumes - content: - name: data - emptyDir: {} - - - it: should have emptyDir data volume if persistence is off - set: - storage: - persistence: false - - asserts: - - hasDocuments: - count: 1 - - contains: - path: .spec.template.spec.volumes - content: - name: data - emptyDir: {} diff --git a/charts/k3s/values.yaml b/charts/k3s/values.yaml deleted file mode 100644 index 70bdbf2b5..000000000 --- a/charts/k3s/values.yaml +++ /dev/null @@ -1,539 +0,0 @@ -# These annotations will be applied to all objects created in this chart -globalAnnotations: {} - -# If vCluster.Pro is enabled -pro: false - -# Defines where vCluster should search for a license secret. If you are using the vCluster.Pro control-plane, -# this is optional. vCluster by default will lookout for a secret called vc-VCLUSTER_NAME-license and if found use that. -# You can also use a secret within another namespace by using the format NAMESPACE/NAME. If the secret is in another -# namespace, please enable clusterRole.create and define an extra clusterRole.extraRules to allow vCluster to retrieve -# secrets within the cluster. -proLicenseSecret: "" - -# If true, will deploy vcluster in headless mode, which means no deployment -# or statefulset is created. -headless: false - -monitoring: - serviceMonitor: - enabled: false - -# DefaultImageRegistry will be prepended to all deployed vcluster images, such as the vcluster pod, coredns etc.. Deployed -# images within the vcluster will not be rewritten. -defaultImageRegistry: "" - -# Plugins that should get loaded. Usually you want to apply those via 'vcluster create ... -f https://.../plugin.yaml' -plugin: {} -# Manually configure a plugin called test -# test: -# image: ... -# env: ... -# rbac: -# clusterRole: -# extraRules: ... -# role: -# extraRules: ... - -# Resource syncers that should be enabled/disabled. -# Enabling syncers will impact RBAC Role and ClusterRole permissions. -# To disable a syncer set "enabled: false". -# See docs for details - https://www.vcluster.com/docs/architecture/synced-resources -sync: - services: - enabled: true - configmaps: - enabled: true - all: false - secrets: - all: false - enabled: true - endpoints: - enabled: true - pods: - enabled: true - ephemeralContainers: false - status: false - events: - enabled: true - persistentvolumeclaims: - enabled: true - ingresses: - enabled: false - ingressclasses: {} - # By default IngressClasses sync is enabled when the Ingress sync is enabled - # but it can be explicitly disabled by setting: - # enabled: false - fake-nodes: - enabled: true # will be ignored if nodes.enabled = true - fake-persistentvolumes: - enabled: true # will be ignored if persistentvolumes.enabled = true - nodes: - fakeKubeletIPs: true - enabled: false - # If nodes sync is enabled, and syncAllNodes = true, the virtual cluster - # will sync all nodes instead of only the ones where some pods are running. - syncAllNodes: false - # nodeSelector is used to limit which nodes get synced to the vcluster, - # and which nodes are used to run vcluster pods. - # A valid string representation of a label selector must be used. - nodeSelector: "" - # if true, vcluster will run with a scheduler and node changes are possible - # from within the virtual cluster. This is useful if you would like to - # taint, drain and label nodes from within the virtual cluster - enableScheduler: false - # DEPRECATED: use enable scheduler instead - # syncNodeChanges allows vcluster user edits of the nodes to be synced down to the host nodes. - # Write permissions on node resource will be given to the vcluster. - syncNodeChanges: false - persistentvolumes: - enabled: false - storageclasses: - enabled: false - # formerly named - "legacy-storageclasses" - hoststorageclasses: - enabled: false - priorityclasses: - enabled: false - networkpolicies: - enabled: false - volumesnapshots: - enabled: false - poddisruptionbudgets: - enabled: false - serviceaccounts: - enabled: false - # generic CRD configuration - generic: - config: |- - --- - -# If enabled, will fallback to host dns for resolving domains. This -# is useful if using istio or dapr in the host cluster and sidecar -# containers cannot connect to the central instance. Its also useful -# if you want to access host cluster services from within the vcluster. -fallbackHostDns: false - -# Map Services between host and virtual cluster -mapServices: - # Services that should get mapped from the - # virtual cluster to the host cluster. - # vcluster will make sure to sync the service - # ip to the host cluster automatically as soon - # as the service exists. - # For example: - # fromVirtual: - # - from: my-namespace/name - # to: host-service - fromVirtual: [] - # Same as from virtual, but instead sync services - # from the host cluster into the virtual cluster. - # If the namespace does not exist, vcluster will - # also create the namespace for the service. - fromHost: [] - -# Proxy metrics server from host to virtual -proxy: - metricsServer: - nodes: - enabled: false - pods: - enabled: false - -# Syncer configuration -syncer: - replicas: 1 - # Image to use for the syncer - # image: ghcr.io/loft-sh/vcluster - extraArgs: [] - imagePullPolicy: "" - env: [] - livenessProbe: - enabled: true - readinessProbe: - enabled: true - volumeMounts: - - mountPath: /data - name: data - # readOnly: true - extraVolumeMounts: [] - resources: - limits: - ephemeral-storage: 8Gi - memory: 2Gi - requests: - ephemeral-storage: 200Mi - cpu: 200m - memory: 256Mi - kubeConfigContextName: "my-vcluster" - serviceAnnotations: {} - # Storage settings for the vcluster - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - -# Virtual Cluster (k3s) configuration -vcluster: - # Image to use for the virtual cluster - image: rancher/k3s:v1.29.0-k3s1 - imagePullPolicy: "" - command: - - /binaries/k3s - baseArgs: - - server - - --write-kubeconfig=/data/k3s-config/kube-config.yaml - - --data-dir=/data - - --disable=traefik,servicelb,metrics-server,local-storage,coredns - - --disable-network-policy - - --disable-agent - - --disable-cloud-controller - - --egress-selector-mode=disabled - - --flannel-backend=none - - --kube-apiserver-arg=bind-address=127.0.0.1 - extraArgs: [] - # Deprecated: Use syncer.extraVolumeMounts instead - extraVolumeMounts: [] - # Deprecated: Use syncer.volumeMounts instead - volumeMounts: [] - # Deprecated: Use syncer.env instead - env: [] - resources: - limits: - cpu: 100m - memory: 256Mi - requests: - cpu: 40m - memory: 64Mi - -# Embedded etcd settings -embeddedEtcd: - # If embedded etcd should be enabled, this is a PRO only feature - enabled: false - -# list of {validating/mutating}webhooks that the syncer should proxy. -# This is a PRO only feature. -centralAdmission: - validatingWebhooks: [] - mutatingWebhooks: [] - - -# Extra volumes that should be created for the StatefulSet -volumes: [] - -# Service account that should be used by the vcluster -serviceAccount: - create: true - # Optional name of the service account to use - # name: default - # Optional pull secrets - # imagePullSecrets: - # - name: my-pull-secret - -# Service account that should be used by the pods synced by vcluster -workloadServiceAccount: - # This is not supported in multi-namespace mode - annotations: {} - -# Roles & ClusterRoles for the vcluster -rbac: - clusterRole: - # You don't need to toggle this as necessary cluster roles are created based on the enabled syncers (.sync.*.enabled). - # This only makes sense to enable if you want to use the extraRules below. - create: false - # Extra Rules for the cluster role - extraRules: [] - role: - # Disable this only if you don't want vCluster to create a role. This will break most functionality if disabled. - create: true - # Extra Rules for the role - extraRules: [] - # all entries in excludedApiResources will be excluded from the Role created for vcluster - excludedApiResources: - # - pods/exec - -# If vCluster persistent volume claims should get deleted automatically. -autoDeletePersistentVolumeClaims: false - -# NodeSelector used to schedule the vcluster -nodeSelector: {} - -# Affinity to apply to the vcluster statefulset -affinity: {} - -# PriorityClassName to apply to the vcluster statefulset -priorityClassName: "" - -# Tolerations to apply to the vcluster statefulset -tolerations: [] - -# Extra Labels for the stateful set -labels: {} -podLabels: {} - -# Extra Annotations for the stateful set -annotations: {} -podAnnotations: {} - -# The k3s token to use. If empty will generate one automatically -k3sToken: "" - -# Service configurations -service: - type: ClusterIP - - # Optional configuration - # A list of IP addresses for which nodes in the cluster will also accept traffic for this service. - # These IPs are not managed by Kubernetes; e.g., an external load balancer. - externalIPs: [] - - # Optional configuration for LoadBalancer & NodePort service types - # Route external traffic to node-local or cluster-wide endpoints [ Local | Cluster ] - externalTrafficPolicy: "" - - # Optional configuration for LoadBalancer service type - # Specify IP of load balancer to be created - loadBalancerIP: "" - # CIDR block(s) for the service allowlist - loadBalancerSourceRanges: [] - # Set the loadBalancerClass if using an external load balancer controller - loadBalancerClass: "" - # Set loadBalancer specific annotations on the Kubernetes service - loadBalancerAnnotations: {} - -# Configure the ingress resource that allows you to access the vcluster -ingress: - # Enable ingress record generation - enabled: false - # Ingress path type - pathType: ImplementationSpecific - ingressClassName: "" - host: vcluster.local - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTPS - nginx.ingress.kubernetes.io/ssl-passthrough: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - # Ingress TLS configuration - tls: [] - # - secretName: tls-vcluster.local - # hosts: - # - vcluster.local - -# Configure SecurityContext of the containers in the VCluster pod -securityContext: - allowPrivilegeEscalation: false - # capabilities: - # drop: - # - all - # readOnlyRootFilesystem will be set to true by default at a later release - # currently leaving it undefined for backwards compatibility with older vcluster cli versions - # readOnlyRootFilesystem: true - - # To run vcluster pod as non-root uncomment runAsUser and runAsNonRoot values. - # Update the runAsUser value if your cluster has limitations on user UIDs. - # For installation on OpenShift leave the runAsUser undefined (commented out). - # runAsUser: 12345 - # runAsNonRoot: true - runAsUser: 0 - runAsGroup: 0 - -# PodSecurityContext holds pod-level security attributes and common container settings for the vCluster pod -podSecurityContext: {} - -# Set "enable" to true when running vcluster in an OpenShift host -# This will add an extra rule to the deployed role binding in order -# to manage service endpoints -openshift: - enable: false - -# If enabled will deploy the coredns configmap -coredns: - # If CoreDns is enabled - enabled: true - # Pro only feature - integrated: false - # only used if isolation is enabled - fallback: 8.8.8.8 - plugin: - enabled: false - config: [] - # example configuration for plugin syntax, will be documented in detail - # - record: - # fqdn: google.com - # target: - # mode: url - # url: google.co.in - # - record: - # service: my-namespace/my-svc # dns-test/nginx-svc - # target: - # mode: host - # service: dns-test/nginx-svc - # - record: - # service: my-namespace-lb/my-svc-lb - # target: - # mode: host - # service: dns-test-exposed-lb/nginx-svc-exposed-lb - # - record: - # service: my-ns-external-name/my-svc-external-name - # target: - # mode: host - # service: dns-test-external-name/nginx-svc-external-name - # - record: - # service: my-ns-in-vcluster/my-svc-vcluster - # target: - # mode: vcluster # can be tested only manually for now - # vcluster: test-vcluster-ns/test-vcluster - # service: dns-test-in-vcluster-ns/test-in-vcluster-service - # - record: - # service: my-ns-in-vcluster-mns/my-svc-mns - # target: - # mode: vcluster # can be tested only manually for now - # service: dns-test-in-vcluster-mns/test-in-vcluster-svc-mns - # vcluster: test-vcluster-ns-mns/test-vcluster-mns - # - record: - # service: my-self-vc-ns/my-self-vc-svc - # target: - # mode: self - # service: dns-test/nginx-svc - replicas: 1 - # The nodeSelector example below specifices that coredns should only be scheduled to nodes with the arm64 label - # nodeSelector: - # kubernetes.io/arch: arm64 - # image: my-core-dns-image:latest - # config: |- - # .:1053 { - # ... - # CoreDNS service configurations - service: - type: ClusterIP - # Configuration for LoadBalancer service type - externalIPs: [] - externalTrafficPolicy: "" - # Extra Annotations - annotations: {} - resources: - limits: - cpu: 1000m - memory: 170Mi - requests: - cpu: 3m - memory: 16Mi -# if below option is configured, it will override the coredns manifests with the following string -# manifests: |- -# apiVersion: ... -# ... - podAnnotations: {} - podLabels: {} - -# If enabled will deploy vcluster in an isolated mode with pod security -# standards, limit ranges and resource quotas -isolation: - enabled: false - namespace: null - - podSecurityStandard: baseline - - # If enabled will add node/proxy permission to the cluster role - # in isolation mode - nodeProxyPermission: - enabled: false - - resourceQuota: - enabled: true - quota: - requests.cpu: 10 - requests.memory: 20Gi - requests.storage: "100Gi" - requests.ephemeral-storage: 60Gi - limits.cpu: 20 - limits.memory: 40Gi - limits.ephemeral-storage: 160Gi - services.nodeports: 0 - services.loadbalancers: 1 - count/endpoints: 40 - count/pods: 20 - count/services: 20 - count/secrets: 100 - count/configmaps: 100 - count/persistentvolumeclaims: 20 - scopeSelector: - matchExpressions: - scopes: - - limitRange: - enabled: true - default: - ephemeral-storage: 8Gi - memory: 512Mi - cpu: "1" - defaultRequest: - ephemeral-storage: 3Gi - memory: 128Mi - cpu: 100m - - networkPolicy: - enabled: true - outgoingConnections: - ipBlock: - cidr: 0.0.0.0/0 - except: - - 100.64.0.0/10 - - 127.0.0.0/8 - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -# manifests to setup when initializing a vcluster -init: - manifests: |- - --- - # The contents of manifests-template will be templated using helm - # this allows you to use helm values inside, e.g.: {{ .Release.Name }} - manifestsTemplate: '' - helm: [] - # - bundle: - base64-encoded .tar.gz file content (optional - overrides chart.repo) - # chart: - # name: REQUIRED - # version: REQUIRED - # repo: (optional when bundle is used) - # username: (if required for repo) - # password: (if required for repo) - # insecure: boolean (if required for repo) - # release: - # name: REQUIRED - # namespace: REQUIRED - # timeout: number - # values: |- string YAML object - # foo: bar - # valuesTemplate: |- string YAML object - # foo: {{ .Release.Name }} - -multiNamespaceMode: - enabled: false - -telemetry: - disabled: false - instanceCreator: "helm" - platformUserID: "" - platformInstanceID: "" - machineID: "" - -noopSyncer: - enabled: false - synck8sService: false - - # Secret containing remote cluster configuration - # (should have the following keys defined) - # serverCaCert: - # serverCaKey: - # clientCaCert: - # requestHeaderCaCert: - # kubeConfig: - # - #secret: "" - diff --git a/charts/k8s/.helmignore b/charts/k8s/.helmignore deleted file mode 100644 index b6a3eb5b3..000000000 --- a/charts/k8s/.helmignore +++ /dev/null @@ -1,24 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store - -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ - -# Common backup files -*.swp -*.bak -*.tmp -*~ - -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/charts/k8s/Chart.yaml b/charts/k8s/Chart.yaml deleted file mode 100644 index 2eda61d64..000000000 --- a/charts/k8s/Chart.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v2 -name: vcluster-k8s -description: vcluster - Virtual Kubernetes Clusters (k8s) -home: https://vcluster.com -icon: https://static.loft.sh/branding/logos/vcluster/vertical/vcluster_vertical.svg -keywords: - - developer - - development - - sharing - - share - - multi-tenancy - - tenancy - - cluster - - space - - namespace - - vcluster - - vclusters -maintainers: - - name: Loft Labs, Inc. - email: info@loft.sh - url: https://twitter.com/loft_sh -sources: - - https://github.com/loft-sh/vcluster -type: application - -version: 0.0.1 # version is auto-generated by release pipeline diff --git a/charts/k8s/README.md b/charts/k8s/README.md deleted file mode 100644 index 48207ef35..000000000 --- a/charts/k8s/README.md +++ /dev/null @@ -1,64 +0,0 @@ - -# vcluster (k8s) - -## **[GitHub](https://github.com/loft-sh/vcluster)** • **[Website](https://www.vcluster.com)** • **[Quickstart](https://www.vcluster.com/docs/getting-started/setup)** • **[Documentation](https://www.vcluster.com/docs/what-are-virtual-clusters)** • **[Blog](https://loft.sh/blog)** • **[Twitter](https://twitter.com/loft_sh)** • **[Slack](https://slack.loft.sh/)** - -Create fully functional virtual Kubernetes clusters - Each vcluster runs inside a namespace of the underlying k8s cluster. It's cheaper than creating separate full-blown clusters and it offers better multi-tenancy and isolation than regular namespaces. - -## Prerequisites - -- Kubernetes 1.18+ -- Helm 3+ - -## Get Helm Repository Info - -```bash -helm repo add loft-sh https://charts.loft.sh -helm repo update -``` - -See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation. - -## Install Helm Chart - -```bash -helm upgrade [RELEASE_NAME] loft-sh/vcluster-k8s -n [RELEASE_NAMESPACE] --create-namespace --install -``` - -See [vcluster docs](https://vcluster.com/docs) for configuration options. - -See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation. - -## Connect to the vcluster - -In order to connect to the installed vcluster, please install [vcluster cli](https://www.vcluster.com/docs/getting-started/setup) and run: - -```bash -vcluster connect [RELEASE_NAME] -n [RELEASE_NAMESPACE] -``` - -## Uninstall Helm Chart - -```bash -helm uninstall [RELEASE_NAME] -``` - -This removes all the Kubernetes components associated with the chart and deletes the release. - -See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation. - -### Why Virtual Kubernetes Clusters? - -- **Cluster Scoped Resources**: much more powerful than simple namespaces (virtual clusters allow users to use CRDs, namespaces, cluster roles etc.) -- **Ease of Use**: usable in any Kubernetes cluster and created in seconds either via a single command or [cluster-api](https://github.com/loft-sh/cluster-api-provider-vcluster) -- **Cost Efficient**: much cheaper and efficient than "real" clusters (single pod and shared resources just like for namespaces) -- **Lightweight**: built upon the ultra-fast k3s distribution with minimal overhead per virtual cluster (other distributions work as well) -- **Strict isolation**: complete separate Kubernetes control plane and access point for each vcluster while still being able to share certain services of the underlying host cluster -- **Cluster Wide Permissions**: allow users to install apps which require cluster-wide permissions while being limited to actually just one namespace within the host cluster -- **Great for Testing**: allow you to test different Kubernetes versions inside a single host cluster which may have a different version than the virtual clusters - -Learn more on [www.vcluster.com](https://vcluster.com). - -![vcluster Intro](https://github.com/loft-sh/vcluster/raw/main/docs/static/media/vcluster-comparison.png) - -Learn more in the [documentation](https://vcluster.com/docs/what-are-virtual-clusters). diff --git a/charts/k8s/templates/NOTES.txt b/charts/k8s/templates/NOTES.txt deleted file mode 100644 index 6cf960d62..000000000 --- a/charts/k8s/templates/NOTES.txt +++ /dev/null @@ -1,10 +0,0 @@ -Thank you for installing vcluster. - -Your vcluster is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}. - -To connect to the vcluster, use vcluster CLI (https://www.vcluster.com/docs/getting-started/setup): - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} - $ vcluster connect {{ .Release.Name }} -n {{ .Release.Namespace }} -- kubectl get ns - - -For more information, please take a look at the vcluster docs at https://www.vcluster.com/docs diff --git a/charts/k8s/templates/_coredns.tpl b/charts/k8s/templates/_coredns.tpl deleted file mode 100644 index f5b1aff93..000000000 --- a/charts/k8s/templates/_coredns.tpl +++ /dev/null @@ -1,52 +0,0 @@ -{{/* - Define a common coredns config -*/}} -{{- define "vcluster.corefile" -}} -Corefile: |- - {{- if .Values.coredns.config }} -{{ .Values.coredns.config | indent 8 }} - {{- else }} - .:1053 { - errors - health - ready - rewrite name regex .*\.nodes\.vcluster\.com kubernetes.default.svc.cluster.local - kubernetes cluster.local in-addr.arpa ip6.arpa { - {{- if .Values.pro }} - {{- if .Values.coredns.integrated }} - kubeconfig /pki/admin.conf - {{- end }} - {{- end }} - pods insecure - {{- if .Values.fallbackHostDns }} - fallthrough cluster.local in-addr.arpa ip6.arpa - {{- else }} - fallthrough in-addr.arpa ip6.arpa - {{- end }} - } - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - vcluster {{ toYaml .Values.coredns.plugin.config | b64enc }} - {{- end }} - hosts /etc/NodeHosts { - ttl 60 - reload 15s - fallthrough - } - prometheus :9153 - {{- if .Values.fallbackHostDns }} - forward . {{`{{.HOST_CLUSTER_DNS}}`}} - {{- else if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} - forward . /etc/resolv.conf {{ .Values.coredns.fallback }} { - policy sequential - } - {{- else }} - forward . /etc/resolv.conf - {{- end }} - cache 30 - loop - loadbalance - } - - import /etc/coredns/custom/*.server - {{- end }} -{{- end -}} diff --git a/charts/k8s/templates/_helpers.tpl b/charts/k8s/templates/_helpers.tpl deleted file mode 100644 index 50f08d6f9..000000000 --- a/charts/k8s/templates/_helpers.tpl +++ /dev/null @@ -1,196 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "vcluster.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Whether the ingressclasses syncer should be enabled -*/}} -{{- define "vcluster.syncIngressclassesEnabled" -}} -{{- if or - (.Values.sync.ingressclasses).enabled - (and - .Values.sync.ingresses.enabled - (not .Values.sync.ingressclasses)) -}} - {{- true -}} -{{- end -}} -{{- end -}} - -{{/* -Whether to create a cluster role or not -*/}} -{{- define "vcluster.createClusterRole" -}} -{{- if or - (not (empty (include "vcluster.serviceMapping.fromHost" . ))) - (not (empty (include "vcluster.plugin.clusterRoleExtraRules" . ))) - (not (empty (include "vcluster.generic.clusterRoleExtraRules" . ))) - .Values.rbac.clusterRole.create - .Values.sync.hoststorageclasses.enabled - (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") - (include "vcluster.syncIngressclassesEnabled" . ) - .Values.pro - .Values.sync.nodes.enabled - .Values.sync.persistentvolumes.enabled - .Values.sync.storageclasses.enabled - .Values.sync.priorityclasses.enabled - .Values.sync.volumesnapshots.enabled - .Values.proxy.metricsServer.nodes.enabled - .Values.multiNamespaceMode.enabled - .Values.coredns.plugin.enabled -}} -{{- true -}} -{{- end -}} -{{- end -}} - -{{- define "vcluster.clusterRoleName" -}} -{{- printf "vc-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "vcluster.clusterRoleNameMultinamespace" -}} -{{- printf "vc-mn-%s-v-%s" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Syncer flags for enabling/disabling controllers -Prints only the flags that modify the defaults: -- when default controller has enabled: false => `- "--sync=-controller` -- when non-default controller has enabled: true => `- "--sync=controller` -*/}} -{{- define "vcluster.syncer.syncArgs" -}} -{{- $defaultEnabled := list "services" "configmaps" "secrets" "endpoints" "pods" "events" "persistentvolumeclaims" "fake-nodes" "fake-persistentvolumes" -}} -{{- if and (hasKey .Values.sync.nodes "enableScheduler") .Values.sync.nodes.enableScheduler -}} - {{- $defaultEnabled = concat $defaultEnabled (list "csinodes" "csidrivers" "csistoragecapacities" ) -}} -{{- end -}} -{{- range $key, $val := .Values.sync }} -{{- if and (has $key $defaultEnabled) (not $val.enabled) }} -- --sync=-{{ $key }} -{{- else if and (not (has $key $defaultEnabled)) ($val.enabled)}} -{{- if eq $key "legacy-storageclasses" }} -- --sync=hoststorageclasses -{{- else }} -- --sync={{ $key }} -{{- end -}} -{{- end -}} -{{- end -}} -{{- if not (include "vcluster.syncIngressclassesEnabled" . ) }} -- --sync=-ingressclasses -{{- end -}} -{{- end -}} - -{{/* - Cluster role rules defined by plugins -*/}} -{{- define "vcluster.plugin.clusterRoleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.clusterRole }} -{{- if $container.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.clusterRoleExtraRules" -}} -{{- if .Values.sync.generic.clusterRole }} -{{- if .Values.sync.generic.clusterRole.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Cluster role rules defined on global level -*/}} -{{- define "vcluster.rbac.clusterRoleExtraRules" -}} -{{- if .Values.rbac.clusterRole.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.clusterRole.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - - -{{/* - Role rules defined on global level -*/}} -{{- define "vcluster.rbac.roleExtraRules" -}} -{{- if .Values.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := .Values.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined by plugins -*/}} -{{- define "vcluster.plugin.roleExtraRules" -}} -{{- range $key, $container := .Values.plugin }} -{{- if $container.rbac }} -{{- if $container.rbac.role }} -{{- if $container.rbac.role.extraRules }} -{{- range $ruleIndex, $rule := $container.rbac.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Role rules defined in generic syncer -*/}} -{{- define "vcluster.generic.roleExtraRules" -}} -{{- if .Values.sync.generic.role }} -{{- if .Values.sync.generic.role.extraRules}} -{{- range $ruleIndex, $rule := .Values.sync.generic.role.extraRules }} -- {{ toJson $rule }} -{{- end }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Virtual cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromVirtual" -}} -{{- range $key, $value := .Values.mapServices.fromVirtual }} -- '--map-virtual-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} - -{{/* - Host cluster service mapping -*/}} -{{- define "vcluster.serviceMapping.fromHost" -}} -{{- range $key, $value := .Values.mapServices.fromHost }} -- '--map-host-service={{ $value.from }}={{ $value.to }}' -{{- end }} -{{- end -}} diff --git a/charts/k8s/templates/_kind.tpl b/charts/k8s/templates/_kind.tpl deleted file mode 100644 index 23009704f..000000000 --- a/charts/k8s/templates/_kind.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* - deployment kind -*/}} -{{- define "vcluster.kind" -}} -{{ if and .Values.embeddedEtcd.enabled .Values.pro }}StatefulSet{{ else }}Deployment{{ end }} -{{- end -}} - -{{/* - service name for statefulset -*/}} -{{- define "vcluster.statefulset.serviceName" }} -{{- if .Values.embeddedEtcd.enabled }} -serviceName: {{ .Release.Name }}-headless -{{- end }} -{{- end -}} - -{{/* - volumeClaimTemplate -*/}} -{{- define "vcluster.statefulset.volumeClaimTemplate" }} -{{- if .Values.embeddedEtcd.enabled }} -{{- if .Values.autoDeletePersistentVolumeClaims }} -{{- if ge (int .Capabilities.KubeVersion.Minor) 27 }} -persistentVolumeClaimRetentionPolicy: - whenDeleted: Delete -{{- end }} -{{- end }} -{{- if (hasKey .Values "volumeClaimTemplates") }} -volumeClaimTemplates: -{{ toYaml .Values.volumeClaimTemplates | indent 4 }} -{{- else if .Values.syncer.storage.persistence }} -volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - {{- if .Values.syncer.storage.className }} - storageClassName: {{ .Values.syncer.storage.className }} - {{- end }} - resources: - requests: - storage: {{ .Values.syncer.storage.size }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - deployment strategy -*/}} -{{- define "vcluster.deployment.strategy" }} -{{- if not .Values.embeddedEtcd.enabled }} -strategy: - rollingUpdate: - maxSurge: 1 - {{- if (eq (int .Values.syncer.replicas) 1) }} - maxUnavailable: 0 - {{- else }} - maxUnavailable: 1 - {{- end }} - type: RollingUpdate -{{- end }} -{{- end -}} diff --git a/charts/k8s/templates/_plugin.tpl b/charts/k8s/templates/_plugin.tpl deleted file mode 100644 index b1ecd2fa2..000000000 --- a/charts/k8s/templates/_plugin.tpl +++ /dev/null @@ -1,188 +0,0 @@ -{{/* - Plugin config definition -*/}} -{{- define "vcluster.plugins.config" -}} -{{- $pluginFound := false -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} -{{- $pluginFound = true -}} -{{- end }} -{{- if $pluginFound }} -- name: PLUGIN_CONFIG - value: |- -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.config) }} -{{- continue }} -{{- end }} - {{ $key }}: {{ toYaml $container.config | nindent 6 }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume mount definition -*/}} -{{- define "vcluster.plugins.volumeMounts" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- mountPath: /plugins - name: plugins -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin volume definition -*/}} -{{- define "vcluster.plugins.volumes" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- name: plugins - emptyDir: {} -{{- break }} -{{- end }} -{{- end -}} - -{{/* - Plugin init container definition -*/}} -{{- define "vcluster.plugins.initContainers" -}} -{{- range $key, $container := .Values.plugin }} -{{- if or (ne $container.version "v2") (not $container.image) }} -{{- continue }} -{{- end }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if or $container.command $container.args }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- else }} - command: ["sh"] - args: ["-c", "cp -r /plugin /plugins/{{ $key }}"] - {{- end }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - {{- if $container.volumeMounts }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- else }} - volumeMounts: - - mountPath: /plugins - name: plugins - {{- end }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* - Extra Syncer Args for the legacy Plugins -*/}} -{{- define "vcluster.legacyPlugins.args" -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{- continue }} -{{- end }} -{{- if not $container.optional }} -- --plugins={{ $key }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* - Sidecar container definition for the legacy syncer parts -*/}} -{{- define "vcluster.legacyPlugins.containers" -}} -{{- $counter := -1 -}} -{{- range $key, $container := .Values.plugin }} -{{- if eq $container.version "v2" }} -{{ continue }} -{{- end }} -{{- $counter = add1 $counter }} -- image: {{ $.Values.defaultImageRegistry }}{{ $container.image }} - {{- if $container.name }} - name: {{ $container.name | quote }} - {{- else }} - name: {{ $key | quote }} - {{- end }} - {{- if $container.imagePullPolicy }} - imagePullPolicy: {{ $container.imagePullPolicy }} - {{- end }} - {{- if $container.workingDir }} - workingDir: {{ $container.workingDir }} - {{- end }} - {{- if $container.command }} - command: - {{- range $commandIndex, $command := $container.command }} - - {{ $command | quote }} - {{- end }} - {{- end }} - {{- if $container.args }} - args: - {{- range $argIndex, $arg := $container.args }} - - {{ $arg | quote }} - {{- end }} - {{- end }} - {{- if $container.terminationMessagePath }} - terminationMessagePath: {{ $container.terminationMessagePath }} - {{- end }} - {{- if $container.terminationMessagePolicy }} - terminationMessagePolicy: {{ $container.terminationMessagePolicy }} - {{- end }} - env: - - name: VCLUSTER_PLUGIN_ADDRESS - value: "localhost:{{ add 14000 $counter }}" - - name: VCLUSTER_PLUGIN_NAME - value: "{{ $key }}" - {{- if $container.env }} -{{ toYaml $container.env | indent 4 }} - {{- end }} - envFrom: -{{ toYaml $container.envFrom | indent 4 }} - securityContext: -{{ toYaml $container.securityContext | indent 4 }} - lifecycle: -{{ toYaml $container.lifecycle | indent 4 }} - livenessProbe: -{{ toYaml $container.livenessProbe | indent 4 }} - readinessProbe: -{{ toYaml $container.readinessProbe | indent 4 }} - startupProbe: -{{ toYaml $container.startupProbe | indent 4 }} - volumeDevices: -{{ toYaml $container.volumeDevices | indent 4 }} - volumeMounts: -{{ toYaml $container.volumeMounts | indent 4 }} - {{- if $container.resources }} - resources: -{{ toYaml $container.resources | indent 4 }} - {{- end }} - {{- end }} -{{- end }} - - diff --git a/charts/k8s/templates/coredns.yaml b/charts/k8s/templates/coredns.yaml deleted file mode 100644 index cc25dd17f..000000000 --- a/charts/k8s/templates/coredns.yaml +++ /dev/null @@ -1,231 +0,0 @@ -{{- if not .Values.headless }} -{{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-coredns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{- if .Values.coredns.manifests }} - coredns.yaml: |- -{{ .Values.coredns.manifests | indent 4 }} -{{- else }} - coredns.yaml: |- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: coredns - namespace: kube-system - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - rules: - - apiGroups: - - "" - resources: - - endpoints - - services - - pods - - namespaces - verbs: - - list - - watch - - apiGroups: - - discovery.k8s.io - resources: - - endpointslices - verbs: - - list - - watch - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - annotations: - rbac.authorization.kubernetes.io/autoupdate: "true" - labels: - kubernetes.io/bootstrapping: rbac-defaults - name: system:coredns - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:coredns - subjects: - - kind: ServiceAccount - name: coredns - namespace: kube-system - --- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: -{{ include "vcluster.corefile" . | indent 6 }} - NodeHosts: "" - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: coredns - namespace: kube-system - labels: - k8s-app: kube-dns - kubernetes.io/name: "CoreDNS" - spec: - replicas: {{ .Values.coredns.replicas }} - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - selector: - matchLabels: - k8s-app: kube-dns - template: - metadata: - {{- if .Values.coredns.podAnnotations }} - annotations: -{{ toYaml .Values.coredns.podAnnotations | indent 12 }} - {{- end }} - labels: - k8s-app: kube-dns - {{- range $k, $v := .Values.coredns.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - priorityClassName: "system-cluster-critical" - serviceAccountName: coredns - nodeSelector: - kubernetes.io/os: linux - {{- if .Values.coredns.nodeSelector }} -{{ toYaml .Values.coredns.nodeSelector | indent 12 }} - {{- end }} - topologySpreadConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: DoNotSchedule - labelSelector: - matchLabels: - k8s-app: kube-dns - {{- if .Values.isolation.enabled }} - securityContext: - seccompProfile: - type: RuntimeDefault - {{- end }} - containers: - - name: coredns - {{- if .Values.coredns.image }} - image: {{ .Values.defaultImageRegistry }}{{ .Values.coredns.image }} - {{- else }} - image: {{`{{.IMAGE}}`}} - {{- end }} - imagePullPolicy: IfNotPresent - resources: -{{ toYaml .Values.coredns.resources | indent 16}} - args: [ "-conf", "/etc/coredns/Corefile" ] - volumeMounts: - - name: config-volume - mountPath: /etc/coredns - readOnly: true - - name: custom-config-volume - mountPath: /etc/coredns/custom - readOnly: true - securityContext: - runAsNonRoot: true - runAsUser: {{`{{.RUN_AS_USER}}`}} - runAsGroup: {{`{{.RUN_AS_GROUP}}`}} - allowPrivilegeEscalation: false - capabilities: - add: - - NET_BIND_SERVICE - drop: - - ALL - readOnlyRootFilesystem: true - livenessProbe: - httpGet: - path: /health - port: 8080 - scheme: HTTP - initialDelaySeconds: 60 - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /ready - port: 8181 - scheme: HTTP - initialDelaySeconds: 0 - periodSeconds: 2 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - dnsPolicy: Default - volumes: - - name: config-volume - configMap: - name: coredns - items: - - key: Corefile - path: Corefile - - key: NodeHosts - path: NodeHosts - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - selector: - k8s-app: kube-dns - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} -{{- end }} diff --git a/charts/k8s/templates/etcd-statefulset-service.yaml b/charts/k8s/templates/etcd-statefulset-service.yaml deleted file mode 100644 index 3ea17586a..000000000 --- a/charts/k8s/templates/etcd-statefulset-service.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{ if or (and (not .Values.embeddedEtcd.enabled) (not .Values.headless) (not .Values.etcd.disabled)) .Values.embeddedEtcd.migrateFromEtcd }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-etcd-headless - namespace: {{ .Release.Namespace }} - labels: - app: vcluster-etcd - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.etcd.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - publishNotReadyAddresses: true - ports: - - name: etcd - port: 2379 - targetPort: 2379 - protocol: TCP - - name: peer - port: 2380 - targetPort: 2380 - protocol: TCP - clusterIP: None - selector: - app: vcluster-etcd - release: "{{ .Release.Name }}" -{{ end }} diff --git a/charts/k8s/templates/ingress.yaml b/charts/k8s/templates/ingress.yaml deleted file mode 100644 index e5caff5b4..000000000 --- a/charts/k8s/templates/ingress.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if .Values.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - {{- $annotations := merge .Values.ingress.annotations .Values.globalAnnotations }} - {{- if .Values.ingress.tls }} - {{- $annotations = omit $annotations "nginx.ingress.kubernetes.io/ssl-passthrough" }} - {{- end }} - {{- if $annotations }} - annotations: - {{ toYaml $annotations | nindent 4 }} - {{- end }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - {{- if .Values.ingress.ingressClassName }} - ingressClassName: {{ .Values.ingress.ingressClassName | quote }} - {{- end }} - rules: - - host: {{ .Values.ingress.host | quote }} - http: - paths: - - backend: - service: - name: {{ .Release.Name }} - port: - name: https - path: / - pathType: {{ .Values.ingress.pathType }} - {{- with .Values.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end -}} -{{- end }} diff --git a/charts/k8s/templates/init-configmap.yaml b/charts/k8s/templates/init-configmap.yaml deleted file mode 100644 index 34498fd14..000000000 --- a/charts/k8s/templates/init-configmap.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.init.manifests }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-init-manifests - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: - manifests: |- - {{ .Values.init.manifests | nindent 4 | trim }} - {{ tpl .Values.init.manifestsTemplate $ | nindent 4 | trim }} - {{- if .Values.init.helm }} - charts: |- - {{- range .Values.init.helm }} - {{- /* only render this chart entry if either of chart or bundle is defined */}} - {{- if .chart }} - - name: {{ .chart.name }} - repo: {{ .chart.repo }} - version: {{ .chart.version }} - {{- if .chart.username }} - username: {{ .chart.username }} - {{- end }} - {{- if .chart.password }} - password: {{ .chart.password }} - {{- end }} - {{- if .insecure }} - insecure: true - {{- end}} - {{- end }} - {{- if .bundle }} - bundle: {{ .bundle }} - {{- end }} - {{- if or .chart .bundle }} - {{- if or .values .valuesTemplate }} - values: |- - {{ (.values | default "") | nindent 8 | trim }} - {{ tpl (.valuesTemplate | default "") $ | nindent 8 | trim }} - {{- end}} - {{- if .release }} - timeout: {{ .timeout | default "120s" | quote }} - releaseName: {{ .release.name }} - releaseNamespace: {{ .release.namespace }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k8s/templates/integrated-coredns.yaml b/charts/k8s/templates/integrated-coredns.yaml deleted file mode 100644 index 16907cba0..000000000 --- a/charts/k8s/templates/integrated-coredns.yaml +++ /dev/null @@ -1,64 +0,0 @@ -{{- if .Values.pro }} -{{- if .Values.coredns.integrated }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-dns - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -data: -{{ include "vcluster.corefile" . | indent 2 }} - coredns.yaml: |- - apiVersion: v1 - kind: ConfigMap - metadata: - name: coredns - namespace: kube-system - data: - NodeHosts: "" - --- - apiVersion: v1 - kind: Service - metadata: - name: kube-dns - namespace: kube-system - annotations: - prometheus.io/port: "9153" - prometheus.io/scrape: "true" - {{- if .Values.coredns.service.annotations }} -{{ toYaml .Values.coredns.service.annotations | indent 8 }} - {{- end }} - labels: - k8s-app: kube-dns - kubernetes.io/cluster-service: "true" - kubernetes.io/name: "CoreDNS" - spec: - type: {{ .Values.coredns.service.type }} - {{- if (eq (.Values.coredns.service.type) "LoadBalancer") }} - {{- if .Values.coredns.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.coredns.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.coredns.service.externalIPs }} - externalIPs: - {{- range $f := .Values.coredns.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: dns - port: 53 - targetPort: 1053 - protocol: UDP - - name: dns-tcp - port: 53 - targetPort: 1053 - protocol: TCP - - name: metrics - port: 9153 - protocol: TCP -{{- end }} -{{- end }} diff --git a/charts/k8s/templates/limitrange.yaml b/charts/k8s/templates/limitrange.yaml deleted file mode 100644 index ed219d063..000000000 --- a/charts/k8s/templates/limitrange.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.limitRange.enabled }} -apiVersion: v1 -kind: LimitRange -metadata: - name: {{ .Release.Name }}-limit-range - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - limits: - - default: - {{- range $key, $val := .Values.isolation.limitRange.default }} - {{ $key }}: {{ $val | quote }} - {{- end }} - defaultRequest: - {{- range $key, $val := .Values.isolation.limitRange.defaultRequest }} - {{ $key }}: {{ $val | quote }} - {{- end }} - type: Container -{{- end }} diff --git a/charts/k8s/templates/networkpolicy.yaml b/charts/k8s/templates/networkpolicy.yaml deleted file mode 100644 index 81886898b..000000000 --- a/charts/k8s/templates/networkpolicy.yaml +++ /dev/null @@ -1,78 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-workloads - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - egress: - # Allows outgoing connections to the vcluster control plane - - ports: - - port: 443 - - port: 8443 - to: - - podSelector: - matchLabels: - release: {{ .Release.Name }} - # Allows outgoing connections to DNS server - - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP - # Allows outgoing connections to the internet or - # other vcluster workloads - - to: - - podSelector: - matchLabels: - vcluster.loft.sh/managed-by: {{ .Release.Name }} - - ipBlock: - cidr: {{ .Values.isolation.networkPolicy.outgoingConnections.ipBlock.cidr }} - except: - {{- range .Values.isolation.networkPolicy.outgoingConnections.ipBlock.except }} - - {{ . }} - {{- end }} - policyTypes: - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ .Release.Name }}-control-plane - namespace: {{ .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - release: {{ .Release.Name }} - egress: - # Allows outgoing connections to all pods with - # port 443, 8443 or 6443. This is needed for host Kubernetes - # access - - ports: - - port: 443 - - port: 8443 - - port: 6443 - # Allows outgoing connections to all vcluster workloads - # or kube system dns server - - to: - - podSelector: {} - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: 'kube-system' - podSelector: - matchLabels: - k8s-app: kube-dns - policyTypes: - - Egress -{{- end }} \ No newline at end of file diff --git a/charts/k8s/templates/rbac/clusterrole.yaml b/charts/k8s/templates/rbac/clusterrole.yaml deleted file mode 100644 index 984267cae..000000000 --- a/charts/k8s/templates/rbac/clusterrole.yaml +++ /dev/null @@ -1,103 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: -{{- if .Values.pro }} - - apiGroups: ["cluster.loft.sh", "storage.loft.sh"] - resources: ["features", "virtualclusters"] - verbs: ["get", "list", "watch"] -{{- end }} - {{- if or .Values.pro .Values.sync.nodes.enabled }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["get", "watch", "list"] - - apiGroups: [""] - resources: [ "pods", "nodes/metrics", "nodes/stats"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.coredns.plugin.enabled }} - - apiGroups: [""] - resources: [ "pods"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled (or (not .Values.isolation.enabled) (and .Values.isolation.nodeProxyPermission.enabled .Values.isolation.enabled)) }} - - apiGroups: [""] - resources: ["nodes/proxy"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if and .Values.sync.nodes.enabled .Values.sync.nodes.syncNodeChanges }} - - apiGroups: [""] - resources: ["nodes", "nodes/status"] - verbs: ["update", "patch"] - {{- end }} - {{- if .Values.sync.persistentvolumes.enabled }} - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if .Values.sync.nodes.enableScheduler }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses","csinodes","csidrivers","csistoragecapacities"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if (include "vcluster.syncIngressclassesEnabled" . ) }} - - apiGroups: ["networking.k8s.io"] - resources: ["ingressclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.storageclasses.enabled }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - {{- end }} - {{- if or .Values.sync.hoststorageclasses.enabled (index ((index .Values.sync "legacy-storageclasses") | default (dict "enabled" false)) "enabled") .Values.rbac.clusterRole.create }} - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.sync.priorityclasses.enabled }} - - apiGroups: ["scheduling.k8s.io"] - resources: ["priorityclasses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if (not (empty (include "vcluster.serviceMapping.fromHost" . ))) }} - - apiGroups: [""] - resources: ["services", "endpoints"] - verbs: ["get", "watch", "list"] - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["create", "delete", "patch", "update", "get", "watch", "list"] - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.proxy.metricsServer.nodes.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["nodes"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.clusterRoleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.clusterRoleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/k8s/templates/rbac/clusterrolebinding.yaml b/charts/k8s/templates/rbac/clusterrolebinding.yaml deleted file mode 100644 index 0a5645d28..000000000 --- a/charts/k8s/templates/rbac/clusterrolebinding.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{- if (include "vcluster.createClusterRole" . ) -}} -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ template "vcluster.clusterRoleName" . }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: - kind: ClusterRole - name: {{ template "vcluster.clusterRoleName" . }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/k8s/templates/rbac/role.yaml b/charts/k8s/templates/rbac/role.yaml deleted file mode 100644 index de05bbb62..000000000 --- a/charts/k8s/templates/rbac/role.yaml +++ /dev/null @@ -1,104 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRole -{{- else -}} -kind: Role -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} -{{- end }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -rules: - {{- if .Values.pro }} - - apiGroups: [""] - {{- $resources := list "configmaps" "secrets" "services" "pods" "pods/attach" "pods/portforward" "pods/exec" "persistentvolumeclaims" }} - {{- range $excluded := .Values.rbac.role.excludedApiResources }} - {{- $resources = without $resources $excluded }} - {{- end}} - resources: {{ $resources | toJson }} - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- else }} - - apiGroups: [""] - resources: ["configmaps", "secrets", "services", "pods", "pods/attach", "pods/portforward", "pods/exec", "persistentvolumeclaims"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.status }} - - apiGroups: [""] - resources: ["pods/status"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.pods.ephemeralContainers }} - - apiGroups: [""] - resources: ["pods/ephemeralcontainers"] - verbs: ["patch", "update"] - {{- end }} - {{- if or .Values.sync.endpoints.enabled .Values.headless }} - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["create", "delete", "patch", "update"] - {{- end }} - {{- if gt (int .Values.syncer.replicas) 1 }} - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: [""] - resources: ["endpoints", "events", "pods/log"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.ingresses.enabled}} - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - - apiGroups: ["apps"] - resources: ["statefulsets", "replicasets", "deployments"] - verbs: ["get", "list", "watch"] - {{- if .Values.sync.networkpolicies.enabled }} - - apiGroups: ["networking.k8s.io"] - resources: ["networkpolicies"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.volumesnapshots.enabled }} - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.serviceaccounts.enabled }} - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.sync.poddisruptionbudgets.enabled }} - - apiGroups: ["policy"] - resources: ["poddisruptionbudgets"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - {{- end }} - {{- if .Values.openshift.enable }} - {{- if .Values.sync.endpoints.enabled }} - - apiGroups: [""] - resources: ["endpoints/restricted"] - verbs: ["create"] - {{- end }} - {{- end }} - {{- if .Values.proxy.metricsServer.pods.enabled }} - - apiGroups: ["metrics.k8s.io"] - resources: ["pods"] - verbs: ["get", "list"] - {{- end }} - {{- include "vcluster.plugin.roleExtraRules" . | indent 2 }} - {{- include "vcluster.generic.roleExtraRules" . | indent 2 }} - {{- include "vcluster.rbac.roleExtraRules" . | indent 2 }} -{{- end }} diff --git a/charts/k8s/templates/rbac/rolebinding.yaml b/charts/k8s/templates/rbac/rolebinding.yaml deleted file mode 100644 index 676e5002a..000000000 --- a/charts/k8s/templates/rbac/rolebinding.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.rbac.role.create }} -{{- if .Values.multiNamespaceMode.enabled }} -kind: ClusterRoleBinding -{{- else -}} -kind: RoleBinding -{{- end }} -apiVersion: rbac.authorization.k8s.io/v1 -metadata: -{{- if .Values.multiNamespaceMode.enabled }} - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -{{- end }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -subjects: - - kind: ServiceAccount - {{- if .Values.serviceAccount.name }} - name: {{ .Values.serviceAccount.name }} - {{- else }} - name: vc-{{ .Release.Name }} - {{- end }} - namespace: {{ .Release.Namespace }} -roleRef: -{{- if .Values.multiNamespaceMode.enabled }} - kind: ClusterRole - name: {{ template "vcluster.clusterRoleNameMultinamespace" . }} -{{- else }} - kind: Role - name: {{ .Release.Name }} -{{- end }} - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/charts/k8s/templates/resourcequota.yaml b/charts/k8s/templates/resourcequota.yaml deleted file mode 100644 index b19d89fff..000000000 --- a/charts/k8s/templates/resourcequota.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if and .Values.isolation.enabled .Values.isolation.resourceQuota.enabled }} -apiVersion: v1 -kind: ResourceQuota -metadata: - name: {{ .Release.Name }}-quota - namespace: {{ .Values.isolation.namespace | default .Release.Namespace }} - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -spec: - hard: - {{- range $key, $val := .Values.isolation.resourceQuota.quota }} - {{ $key }}: {{ $val | quote }} - {{- end }} - - {{- if .Values.isolation.resourceQuota.scopeSelector.matchExpressions }} - scopeSelector: - matchExpressions: - {{- toYaml .Values.isolation.resourceQuota.scopeSelector.matchExpressions | nindent 4 }} - {{- end}} - - {{- if .Values.isolation.resourceQuota.scopes }} - scopes: - {{- toYaml .Values.isolation.resourceQuota.scopes | nindent 4 }} - {{- end}} - -{{- end }} diff --git a/charts/k8s/templates/service-monitor.yaml b/charts/k8s/templates/service-monitor.yaml deleted file mode 100644 index b6917ae27..000000000 --- a/charts/k8s/templates/service-monitor.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if .Values.monitoring.serviceMonitor.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} -spec: - selector: - matchLabels: - app: vcluster - release: "{{ .Release.Name }}" - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - heritage: "{{ .Release.Service }}" - endpoints: - - interval: 30s - port: https - path: /metrics - scheme: https - tlsConfig: - ca: - secret: - name: vc-{{ .Release.Name }} - key: certificate-authority - cert: - secret: - name: vc-{{ .Release.Name }} - key: client-certificate - keySecret: - name: vc-{{ .Release.Name }} - key: client-key - serverName: 127.0.0.1 -{{- end }} diff --git a/charts/k8s/templates/service.yaml b/charts/k8s/templates/service.yaml deleted file mode 100644 index b575a9fe1..000000000 --- a/charts/k8s/templates/service.yaml +++ /dev/null @@ -1,93 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: {{ if eq .Values.service.type "LoadBalancer" -}}ClusterIP{{- else }} {{- .Values.service.type }} {{- end }} - ports: - - name: https - port: 443 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.httpsNodePort }} - protocol: TCP - - name: kubelet - port: 10250 - {{- if not .Values.headless }} - targetPort: 8443 - {{- end }} - nodePort: {{ .Values.service.kubeletNodePort }} - protocol: TCP - {{- if .Values.service.externalIPs }} - externalIPs: - {{- range $f := .Values.service.externalIPs }} - - {{ $f }} - {{- end }} - {{- end }} - {{- if eq .Values.service.type "NodePort" }} - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} ---- -{{ if eq .Values.service.type "LoadBalancer" }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-lb - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.service.loadBalancerAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - type: LoadBalancer - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} - {{- end }} - {{- if not .Values.headless }} - selector: - app: vcluster - release: {{ .Release.Name }} - {{- end }} - {{- if .Values.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.service.loadBalancerIP }} - {{- end }} - {{- if .Values.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.service.loadBalancerClass }} - {{- end }} - {{- if .Values.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: - {{- range $f := .Values.service.loadBalancerSourceRanges }} - - "{{ $f }}" - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/k8s/templates/serviceaccount.yaml b/charts/k8s/templates/serviceaccount.yaml deleted file mode 100644 index 504998037..000000000 --- a/charts/k8s/templates/serviceaccount.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.serviceAccount.create }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- if .Values.globalAnnotations }} - annotations: -{{ toYaml .Values.globalAnnotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} -{{- end }} diff --git a/charts/k8s/templates/statefulset-service.yaml b/charts/k8s/templates/statefulset-service.yaml deleted file mode 100644 index e7b5d3d91..000000000 --- a/charts/k8s/templates/statefulset-service.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if eq ( include "vcluster.kind" . ) "StatefulSet" }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-headless - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "vcluster.fullname" . }} - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.serviceAnnotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - publishNotReadyAddresses: true - ports: - - name: https - port: 443 - targetPort: 8443 - protocol: TCP - {{- if .Values.embeddedEtcd.enabled }} - - name: etcd - port: 2379 - targetPort: 2379 - protocol: TCP - - name: peer - port: 2380 - targetPort: 2380 - protocol: TCP - {{- end }} - clusterIP: None - selector: - app: vcluster - release: "{{ .Release.Name }}" -{{- end }} diff --git a/charts/k8s/templates/syncer.yaml b/charts/k8s/templates/syncer.yaml deleted file mode 100644 index a59945763..000000000 --- a/charts/k8s/templates/syncer.yaml +++ /dev/null @@ -1,493 +0,0 @@ -{{- if not .Values.headless }} -apiVersion: apps/v1 -kind: {{ include "vcluster.kind" . }} -metadata: - name: {{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -{{- if .Values.syncer.labels }} -{{ toYaml .Values.syncer.labels | indent 4 }} -{{- end }} - {{- $annotations := merge .Values.globalAnnotations .Values.syncer.annotations }} - {{- if $annotations}} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -spec: - replicas: {{ .Values.syncer.replicas }} - {{- include "vcluster.deployment.strategy" . | indent 2 }} - {{- include "vcluster.statefulset.serviceName" . | indent 2 }} - {{- include "vcluster.statefulset.volumeClaimTemplate" . | indent 2 }} - selector: - matchLabels: - app: vcluster - release: {{ .Release.Name }} - template: - metadata: - {{- if .Values.syncer.podAnnotations }} - annotations: -{{ toYaml .Values.syncer.podAnnotations | indent 8 }} - {{- end }} - labels: - app: vcluster - release: {{ .Release.Name }} - {{- range $k, $v := .Values.syncer.podLabels }} - {{ $k }}: {{ $v | quote }} - {{- end }} - spec: - terminationGracePeriodSeconds: 10 - {{- if .Values.syncer.affinity }} - affinity: -{{ toYaml .Values.syncer.affinity | indent 8 }} - {{- else if (gt (int .Values.syncer.replicas) 1) }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - # if possible avoid scheduling more than one pod on one node - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: "kubernetes.io/hostname" - # if possible avoid scheduling pod onto node that is in the same zone as one or more vcluster pods are running - - weight: 50 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - vcluster - - key: release - operator: In - values: - - {{ .Release.Name }} - topologyKey: topology.kubernetes.io/zone - {{- end }} - {{- if .Values.syncer.topologySpreadConstraints }} - topologySpreadConstraints: -{{ toYaml .Values.syncer.topologySpreadConstraints | indent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.syncer.nodeSelector | indent 8 }} - tolerations: -{{ toYaml .Values.syncer.tolerations | indent 8 }} - {{- if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} - {{- else }} - serviceAccountName: vc-{{ .Release.Name }} - {{- end }} - {{- if .Values.syncer.podSecurityContext }} - securityContext: -{{ toYaml .Values.syncer.podSecurityContext | indent 8 }} - {{- end }} - volumes: - {{- include "vcluster.plugins.volumes" . | indent 8 }} - - name: helm-cache - emptyDir: {} - - name: tmp - emptyDir: {} - - name: certs - emptyDir: {} - {{- if .Values.volumes }} -{{ toYaml .Values.volumes | indent 8 }} - {{- end }} - - name: binaries - emptyDir: {} - {{- if .Values.syncer.volumes }} -{{ toYaml .Values.syncer.volumes | indent 8 }} - {{- end }} - {{- if and .Values.coredns.enabled (not .Values.coredns.integrated) }} - - name: coredns - configMap: - name: {{ .Release.Name }}-coredns - {{- else if .Values.coredns.integrated }} - - name: coredns - configMap: - name: {{ .Release.Name }}-dns - {{- end }} - - name: custom-config-volume - configMap: - name: coredns-custom - optional: true - {{- if .Values.syncer.priorityClassName }} - priorityClassName: {{ .Values.syncer.priorityClassName }} - {{- end }} - initContainers: - {{- include "vcluster.plugins.initContainers" . | indent 6 }} - # this is needed because the k8s containers are distroless and thus we don't have any - # way of copying the binaries otherwise - - name: vcluster-copy - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /bin/sh - args: - - -c - - "cp /vcluster /binaries/vcluster" - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- if not .Values.controller.disabled }} - - name: kube-controller-manager - image: "{{ .Values.defaultImageRegistry }}{{ .Values.controller.image }}" - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /binaries/vcluster - args: - - cp - - /usr/local/bin/kube-controller-manager - - /binaries/kube-controller-manager - {{- if .Values.controller.imagePullPolicy }} - imagePullPolicy: {{ .Values.controller.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} - {{- if or (not .Values.scheduler.disabled) .Values.sync.nodes.enableScheduler }} - - name: kube-scheduler-manager - image: "{{ .Values.defaultImageRegistry }}{{ .Values.scheduler.image }}" - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /binaries/vcluster - args: - - cp - - /usr/local/bin/kube-scheduler - - /binaries/kube-scheduler - {{- if .Values.scheduler.imagePullPolicy }} - imagePullPolicy: {{ .Values.scheduler.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} - {{- if not .Values.api.disabled }} - - name: kube-apiserver - image: "{{ .Values.defaultImageRegistry }}{{ .Values.api.image }}" - volumeMounts: - - mountPath: /binaries - name: binaries - command: - - /binaries/vcluster - args: - - cp - - /usr/local/bin/kube-apiserver - - /binaries/kube-apiserver - {{- if .Values.api.imagePullPolicy }} - imagePullPolicy: {{ .Values.api.imagePullPolicy }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} - {{- end }} - containers: - - name: syncer - {{- if .Values.syncer.image }} - image: "{{ .Values.defaultImageRegistry }}{{ .Values.syncer.image }}" - {{- else }} - {{- if .Values.pro }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster-pro:{{ .Chart.Version }}" - {{- else }} - image: "{{ .Values.defaultImageRegistry }}ghcr.io/loft-sh/vcluster:{{ .Chart.Version }}" - {{- end }} - {{- end }} - {{- if .Values.syncer.workingDir }} - workingDir: {{ .Values.syncer.workingDir }} - {{- end }} - {{- if .Values.syncer.command }} - command: - {{- range $f := .Values.syncer.command }} - - {{ $f | quote }} - {{- end }} - {{- end }} - args: - - --name={{ .Release.Name }} - - --request-header-ca-cert=/pki/front-proxy-ca.crt - - --client-ca-cert=/pki/ca.crt - - --server-ca-cert=/pki/ca.crt - - --server-ca-key=/pki/ca.key - - --kube-config=/pki/admin.conf - - --service-account=vc-workload-{{ .Release.Name }} - {{- if .Values.embeddedEtcd.migrateFromEtcd }} - - --migrate-from=https://{{ .Release.Name }}-etcd:2379 - {{- end }} - {{- include "vcluster.legacyPlugins.args" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromHost" . | indent 10 }} - {{- include "vcluster.serviceMapping.fromVirtual" . | indent 10 }} - {{- if or (not .Values.scheduler.disabled) .Values.sync.nodes.enableScheduler }} - - --enable-scheduler - {{- end }} - {{- if .Values.defaultImageRegistry }} - - --default-image-registry={{ .Values.defaultImageRegistry }} - {{- end }} - {{- if .Values.syncer.kubeConfigContextName }} - - --kube-config-context-name={{ .Values.syncer.kubeConfigContextName }} - {{- end }} - {{- if .Values.pro }} - {{- if .Values.proLicenseSecret }} - - --pro-license-secret={{ .Values.proLicenseSecret }} - {{- end }} - {{- if .Values.embeddedEtcd.enabled }} - - --etcd-embedded - - --etcd-replicas={{ .Values.syncer.replicas }} - {{- end }} - {{- end }} - {{- if (gt (int .Values.syncer.replicas) 1)}} - - --leader-elect=true - {{- else }} - - --leader-elect=false - {{- end }} - {{- if .Values.ingress.enabled }} - - --tls-san={{ .Values.ingress.host }} - {{- end }} - {{- if .Values.isolation.enabled }} - - --enforce-pod-security-standard={{ .Values.isolation.podSecurityStandard }} - {{- end}} - {{- include "vcluster.syncer.syncArgs" . | indent 10 -}} - {{- if .Values.sync.nodes.syncAllNodes }} - - --sync-all-nodes - {{- end }} - {{- if .Values.sync.nodes.nodeSelector }} - - --node-selector={{ .Values.sync.nodes.nodeSelector }} - {{- end }} - {{- if .Values.multiNamespaceMode.enabled }} - - --multi-namespace-mode=true - {{- end }} - {{- if .Values.sync.configmaps.all }} - - --sync-all-configmaps=true - {{- end }} - {{- if .Values.sync.secrets.all }} - - --sync-all-secrets=true - {{- end }} - {{- if not .Values.sync.nodes.fakeKubeletIPs }} - - --fake-kubelet-ips=false - {{- end }} - {{- if or .Values.proxy.metricsServer.nodes.enabled .Values.proxy.metricsServer.pods.enabled }} - - --proxy-metrics-server=true - {{- end }} - {{- if .Values.coredns.integrated }} - - --integrated-coredns=true - {{- end }} - {{- if and .Values.coredns.integrated .Values.coredns.plugin.enabled }} - - --use-coredns-plugin=true - {{- end }} - {{- if .Values.centralAdmission.validatingWebhooks }} - {{- range .Values.centralAdmission.validatingWebhooks }} - - --enforce-validating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- if .Values.centralAdmission.mutatingWebhooks }} - {{- range .Values.centralAdmission.mutatingWebhooks }} - - --enforce-mutating-hook={{ . | toYaml | b64enc }} - {{- end }} - {{- end }} - {{- range $f := .Values.syncer.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- if .Values.syncer.livenessProbe }} - {{- if .Values.syncer.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: /healthz - port: 8443 - scheme: HTTPS - failureThreshold: 10 - initialDelaySeconds: 60 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.readinessProbe }} - {{- if .Values.syncer.readinessProbe.enabled }} - startupProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 300 - periodSeconds: 6 - readinessProbe: - httpGet: - path: /readyz - port: 8443 - scheme: HTTPS - failureThreshold: 30 - periodSeconds: 2 - {{- end }} - {{- end }} - {{- if .Values.syncer.imagePullPolicy }} - imagePullPolicy: {{ .Values.syncer.imagePullPolicy }} - {{- end }} - securityContext: -{{ toYaml .Values.syncer.securityContext | indent 10 }} - env: - {{- include "vcluster.plugins.config" . | indent 10 }} - - name: VCLUSTER_DISTRO - value: k8s - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if eq (.Values.syncer.replicas | toString | atoi) 1 }} - - name: VCLUSTER_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - {{- end }} - {{- if .Values.syncer.env }} -{{ toYaml .Values.syncer.env | indent 10 }} - {{- end }} - {{- if .Values.sync.generic.config }} - - name: CONFIG - value: |- - {{- .Values.sync.generic.config | nindent 14 }} - {{- end }} - - name: VCLUSTER_TELEMETRY_CONFIG - value: {{ .Values.telemetry | toJson | quote }} - {{- if not .Values.api.disabled }} - - name: APISERVER_COMMAND - value: |- - command: - - /binaries/kube-apiserver - - '--advertise-address=127.0.0.1' - - '--bind-address=127.0.0.1' - - '--allow-privileged=true' - - '--authorization-mode=RBAC' - - '--client-ca-file=/pki/ca.crt' - - '--enable-bootstrap-token-auth=true' - - '--etcd-cafile=/pki/etcd/ca.crt' - - '--etcd-certfile=/pki/apiserver-etcd-client.crt' - - '--etcd-keyfile=/pki/apiserver-etcd-client.key' - {{- if .Values.embeddedEtcd.enabled }} - - '--etcd-servers=https://127.0.0.1:2379' - {{- else }} - - '--etcd-servers=https://{{ .Release.Name }}-etcd:2379' - {{- end }} - - '--proxy-client-cert-file=/pki/front-proxy-client.crt' - - '--proxy-client-key-file=/pki/front-proxy-client.key' - - '--requestheader-allowed-names=front-proxy-client' - - '--requestheader-client-ca-file=/pki/front-proxy-ca.crt' - - '--requestheader-extra-headers-prefix=X-Remote-Extra-' - - '--requestheader-group-headers=X-Remote-Group' - - '--requestheader-username-headers=X-Remote-User' - - '--secure-port=6443' - - '--service-account-issuer=https://kubernetes.default.svc.cluster.local' - - '--service-account-key-file=/pki/sa.pub' - - '--service-account-signing-key-file=/pki/sa.key' - - '--tls-cert-file=/pki/apiserver.crt' - - '--tls-private-key-file=/pki/apiserver.key' - - '--watch-cache=false' - - '--endpoint-reconciler-type=none' - {{- range $f := .Values.api.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- end }} - {{- if not .Values.controller.disabled }} - - name: CONTROLLER_COMMAND - value: |- - command: - - /binaries/kube-controller-manager - - '--authentication-kubeconfig=/pki/controller-manager.conf' - - '--authorization-kubeconfig=/pki/controller-manager.conf' - - '--bind-address=127.0.0.1' - - '--client-ca-file=/pki/ca.crt' - - '--cluster-name=kubernetes' - - '--cluster-signing-cert-file=/pki/ca.crt' - - '--cluster-signing-key-file=/pki/ca.key' - {{- if or (not .Values.scheduler.disabled) .Values.sync.nodes.enableScheduler }} - - '--controllers=*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' - - '--node-monitor-grace-period=1h' - - '--node-monitor-period=1h' - {{- else }} - - '--controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' - {{- end }} - - '--horizontal-pod-autoscaler-sync-period=60s' - - '--kubeconfig=/pki/controller-manager.conf' - {{- if (gt (int .Values.syncer.replicas) 1) }} - - '--leader-elect=true' - {{- else }} - - '--leader-elect=false' - {{- end }} - - '--node-monitor-grace-period=180s' - - '--node-monitor-period=30s' - - '--pvclaimbinder-sync-period=60s' - - '--requestheader-client-ca-file=/pki/front-proxy-ca.crt' - - '--root-ca-file=/pki/ca.crt' - - '--service-account-private-key-file=/pki/sa.key' - - '--use-service-account-credentials=true' - {{- range $f := .Values.controller.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- end }} - {{- if or (not .Values.scheduler.disabled) .Values.sync.nodes.enableScheduler }} - - name: SCHEDULER_COMMAND - value: |- - command: - - /binaries/kube-scheduler - - '--authentication-kubeconfig=/pki/scheduler.conf' - - '--authorization-kubeconfig=/pki/scheduler.conf' - - '--bind-address=127.0.0.1' - - '--kubeconfig=/pki/scheduler.conf' - {{- if (gt (int .Values.syncer.replicas) 1) }} - - '--leader-elect=true' - {{- else }} - - '--leader-elect=false' - {{- end }} - {{- range $f := .Values.scheduler.extraArgs }} - - {{ $f | quote }} - {{- end }} - {{- end }} - volumeMounts: - {{- include "vcluster.plugins.volumeMounts" . | indent 10 }} - {{- if eq ( include "vcluster.kind" . ) "StatefulSet" }} - - name: data - mountPath: /data - {{- end }} - - name: helm-cache - mountPath: /.cache/helm - - name: tmp - mountPath: /tmp - - mountPath: /pki - name: certs - - mountPath: /binaries - name: binaries - {{- if .Values.coredns.enabled }} - - name: coredns - mountPath: /manifests/coredns - readOnly: true - {{- end }} - {{- if .Values.syncer.volumeMounts }} -{{ toYaml .Values.syncer.volumeMounts | indent 10 }} - {{- end }} - {{- if .Values.syncer.extraVolumeMounts }} -{{ toYaml .Values.syncer.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.syncer.resources | indent 10 }} -{{- include "vcluster.legacyPlugins.containers" . | indent 6 }} -{{- end }} diff --git a/charts/k8s/templates/workloadserviceaccount.yaml b/charts/k8s/templates/workloadserviceaccount.yaml deleted file mode 100644 index 8d05fc1e8..000000000 --- a/charts/k8s/templates/workloadserviceaccount.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: vc-workload-{{ .Release.Name }} - namespace: {{ .Release.Namespace }} - labels: - app: vcluster - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" - {{- $annotations := merge .Values.globalAnnotations .Values.workloadServiceAccount.annotations }} - {{- if $annotations }} - annotations: -{{ toYaml $annotations | indent 4 }} - {{- end }} -{{- if .Values.serviceAccount.imagePullSecrets }} -imagePullSecrets: -{{ toYaml .Values.serviceAccount.imagePullSecrets | indent 2 }} -{{- end }} diff --git a/charts/k8s/tests/README.md b/charts/k8s/tests/README.md deleted file mode 100644 index 24d888a08..000000000 --- a/charts/k8s/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -Add [unittest plugin](https://github.com/helm-unittest/helm-unittest) via: -``` -helm plugin install https://github.com/helm-unittest/helm-unittest.git -``` - -Run tests via: -``` -helm unittest charts/k8s -d -``` diff --git a/charts/k8s/tests/clusterrole_test.yaml b/charts/k8s/tests/clusterrole_test.yaml deleted file mode 100644 index 3385c786c..000000000 --- a/charts/k8s/tests/clusterrole_test.yaml +++ /dev/null @@ -1,41 +0,0 @@ -suite: ClusterRole -templates: - - rbac/clusterrole.yaml - -tests: - - it: should create clusterrole - set: - rbac: - clusterRole: - create: true - asserts: - - hasDocuments: - count: 1 - - it: should not create clusterrole - set: - rbac: - clusterRole: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - clusterRole: - create: true - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k8s/tests/etcd_test.yaml b/charts/k8s/tests/etcd_test.yaml deleted file mode 100644 index 931ce5d34..000000000 --- a/charts/k8s/tests/etcd_test.yaml +++ /dev/null @@ -1,26 +0,0 @@ -suite: Etcd -templates: - - etcd-statefulset.yaml - - etcd-service.yaml - - etcd-statefulset-service.yaml - -tests: - - it: should have etcd when migratefrometcd is true - set: - pro: true - embeddedEtcd: - enabled: true - migrateFromEtcd: true - asserts: - - hasDocuments: - count: 1 - - it: shouldn't have etcd when migratefrometcd is false - set: - pro: true - embeddedEtcd: - enabled: true - migrateFromEtcd: false - asserts: - - hasDocuments: - count: 0 - diff --git a/charts/k8s/tests/legacy-plugins_test.yaml b/charts/k8s/tests/legacy-plugins_test.yaml deleted file mode 100644 index 6977c7562..000000000 --- a/charts/k8s/tests/legacy-plugins_test.yaml +++ /dev/null @@ -1,77 +0,0 @@ -suite: Legacy Plugins -templates: - - syncer.yaml - -tests: - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - - it: should check legacy plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - bootstrap-with-deployment2: - image: test - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment" - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--plugins=bootstrap-with-deployment2" - count: 1 - - equal: - path: spec.template.spec.containers[1].name - value: bootstrap-with-deployment - - equal: - path: spec.template.spec.containers[2].name - value: bootstrap-with-deployment2 - - equal: - path: spec.template.spec.containers[1].image - value: test - - equal: - path: spec.template.spec.containers[2].image - value: test - - equal: - path: spec.template.spec.containers[1].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14000 - - equal: - path: spec.template.spec.containers[2].env[0] - value: - name: VCLUSTER_PLUGIN_ADDRESS - value: localhost:14001 - - - it: should check no legacy plugin rendering - asserts: - - hasDocuments: - count: 1 - - lengthEqual: - path: spec.template.spec.containers - count: 1 diff --git a/charts/k8s/tests/plugins_test.yaml b/charts/k8s/tests/plugins_test.yaml deleted file mode 100644 index 2ce3149dc..000000000 --- a/charts/k8s/tests/plugins_test.yaml +++ /dev/null @@ -1,100 +0,0 @@ -suite: Plugins -templates: - - syncer.yaml - -tests: - - it: should check plugin config rendering - set: - plugin: - plugin1: - version: v2 - config: - myConfig: true - plugin2: - version: v2 - image: test - plugin3: - version: v2 - image: test123 - config: - myOtherConfig: - - test123 - - test456 - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.containers[0].env[0].name - value: PLUGIN_CONFIG - - equal: - path: spec.template.spec.containers[0].env[0].value - value: |- - plugin1: - myConfig: true - plugin3: - myOtherConfig: - - test123 - - test456 - - - it: should check plugin rendering - set: - plugin: - bootstrap-with-deployment: - version: v2 - image: test - asserts: - - hasDocuments: - count: 1 - - equal: - path: spec.template.spec.initContainers[0].image - value: test - - equal: - path: spec.template.spec.volumes[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - set: - plugin: - bootstrap-with-deployment: - image: test - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins - - - it: should check no plugin rendering - asserts: - - hasDocuments: - count: 1 - - notEqual: - path: spec.template.spec.initContainers[0].image - value: test - - notEqual: - path: spec.template.spec.volumes[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].name - value: plugins - - notEqual: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /plugins diff --git a/charts/k8s/tests/role_test.yaml b/charts/k8s/tests/role_test.yaml deleted file mode 100644 index 5eeedba74..000000000 --- a/charts/k8s/tests/role_test.yaml +++ /dev/null @@ -1,32 +0,0 @@ -suite: Role -templates: - - rbac/role.yaml - -tests: - - it: should not create role - set: - rbac: - role: - create: false - asserts: - - hasDocuments: - count: 0 - - it: should contain extra rule - set: - rbac: - role: - extraRules: - - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - asserts: - - hasDocuments: - count: 1 - - contains: - path: rules - content: - apiGroups: ["test"] - resources: ["tests"] - verbs: ["test"] - count: 1 - diff --git a/charts/k8s/tests/syncer_test.yaml b/charts/k8s/tests/syncer_test.yaml deleted file mode 100644 index 7713b2e10..000000000 --- a/charts/k8s/tests/syncer_test.yaml +++ /dev/null @@ -1,52 +0,0 @@ -suite: Syncer -templates: - - syncer.yaml - -tests: - - it: should pass pro license secret as a flag - set: - pro: true - proLicenseSecret: "my-test-secret" - asserts: - - hasDocuments: - count: 1 - - contains: - path: spec.template.spec.containers[0].args - content: "--pro-license-secret=my-test-secret" - count: 1 - - it: should be a statefulset when embeddedEtcd is enabled - set: - pro: true - embeddedEtcd: - enabled: true - asserts: - - hasDocuments: - count: 1 - - isKind: - of: StatefulSet - - exists: - path: spec.serviceName - - exists: - path: spec.volumeClaimTemplates - - notExists: - path: spec.strategy - - equal: - path: spec.template.spec.containers[0].volumeMounts[0].mountPath - value: /data - - - it: should be a deployment when embeddedEtcd is disabled - set: - pro: true - embeddedEtcd: - enabled: false - asserts: - - hasDocuments: - count: 1 - - isKind: - of: Deployment - - notExists: - path: spec.serviceName - - notExists: - path: spec.volumeClaimTemplates - - exists: - path: spec.strategy diff --git a/charts/k8s/values.yaml b/charts/k8s/values.yaml deleted file mode 100644 index 7d076e05d..000000000 --- a/charts/k8s/values.yaml +++ /dev/null @@ -1,524 +0,0 @@ -# DefaultImageRegistry will be prepended to all deployed vcluster images, such as the vcluster pod, coredns etc.. Deployed -# images within the vcluster will not be rewritten. -defaultImageRegistry: "" - -# Global annotations to add to all objects -globalAnnotations: {} - -# If vCluster.Pro is enabled -pro: false - -# Defines where vCluster should search for a license secret. If you are using the vCluster.Pro control-plane, -# this is optional. vCluster by default will lookout for a secret called vc-VCLUSTER_NAME-license and if found use that. -# You can also use a secret within another namespace by using the format NAMESPACE/NAME. If the secret is in another -# namespace, please enable clusterRole.create and define an extra clusterRole.extraRules to allow vCluster to retrieve -# secrets within the cluster. -proLicenseSecret: "" - -# Embedded etcd settings -embeddedEtcd: - # If embedded etcd should be enabled, this is a PRO only feature - enabled: false - # To use if embeddedEtcd is enabled and you want to migrate the data - # from the external etcd - migrateFromEtcd: false - -# If true, will deploy vcluster in headless mode, which means no deployment -# or statefulset is created. -headless: false - -monitoring: - serviceMonitor: - enabled: false - -# Plugins that should get loaded. Usually you want to apply those via 'vcluster create ... -f https://.../plugin.yaml' -plugin: {} -# Manually configure a plugin called test -# test: -# image: ... -# env: ... -# rbac: -# clusterRole: -# extraRules: ... -# role: -# extraRules: ... - -# Extra Annotations for the stateful set -annotations: {} -podAnnotations: {} - -# Resource syncers that should be enabled/disabled. -# Enabling syncers will impact RBAC Role and ClusterRole permissions. -# To disable a syncer set "enabled: false". -# See docs for details - https://www.vcluster.com/docs/architecture/synced-resources -sync: - services: - enabled: true - configmaps: - enabled: true - all: false - secrets: - enabled: true - all: false - endpoints: - enabled: true - pods: - enabled: true - ephemeralContainers: false - status: false - events: - enabled: true - persistentvolumeclaims: - enabled: true - ingresses: - enabled: false - ingressclasses: {} - # By default IngressClasses sync is enabled when the Ingress sync is enabled - # but it can be explicitly disabled by setting: - # enabled: false - fake-nodes: - enabled: true # will be ignored if nodes.enabled = true - fake-persistentvolumes: - enabled: true # will be ignored if persistentvolumes.enabled = true - nodes: - fakeKubeletIPs: true - enabled: false - # If nodes sync is enabled, and syncAllNodes = true, the virtual cluster - # will sync all nodes instead of only the ones where some pods are running. - syncAllNodes: false - # nodeSelector is used to limit which nodes get synced to the vcluster, - # and which nodes are used to run vcluster pods. - # A valid string representation of a label selector must be used. - nodeSelector: "" - # if true, vcluster will run with a scheduler and node changes are possible - # from within the virtual cluster. This is useful if you would like to - # taint, drain and label nodes from within the virtual cluster - enableScheduler: false - # DEPRECATED: use enable scheduler instead - # syncNodeChanges allows vcluster user edits of the nodes to be synced down to the host nodes. - # Write permissions on node resource will be given to the vcluster. - syncNodeChanges: false - persistentvolumes: - enabled: false - storageclasses: - enabled: false - # formerly named - "legacy-storageclasses" - hoststorageclasses: - enabled: false - priorityclasses: - enabled: false - networkpolicies: - enabled: false - volumesnapshots: - enabled: false - poddisruptionbudgets: - enabled: false - serviceaccounts: - enabled: false - # generic CRD configuration - generic: - config: |- - --- - -# If enabled, will fallback to host dns for resolving domains. This -# is useful if using istio or dapr in the host cluster and sidecar -# containers cannot connect to the central instance. Its also useful -# if you want to access host cluster services from within the vcluster. -fallbackHostDns: false - -# Map Services between host and virtual cluster -mapServices: - # Services that should get mapped from the - # virtual cluster to the host cluster. - # vcluster will make sure to sync the service - # ip to the host cluster automatically as soon - # as the service exists. - # For example: - # fromVirtual: - # - from: my-namespace/name - # to: host-service - fromVirtual: [] - # Same as from virtual, but instead sync services - # from the host cluster into the virtual cluster. - # If the namespace does not exist, vcluster will - # also create the namespace for the service. - fromHost: [] - -proxy: - metricsServer: - nodes: - enabled: false - pods: - enabled: false - -# Syncer configuration -syncer: - # Image to use for the syncer - # image: ghcr.io/loft-sh/vcluster - imagePullPolicy: "" - extraArgs: [] - volumeMounts: [] - extraVolumeMounts: [] - env: [] - livenessProbe: - enabled: true - readinessProbe: - enabled: true - resources: - limits: - ephemeral-storage: 8Gi - cpu: 1000m - memory: 2Gi - requests: - ephemeral-storage: 200Mi - # ensure that cpu/memory requests are high enough. - # for example gke wants minimum 10m/32Mi here! - cpu: 20m - memory: 256Mi - # Extra volumes - volumes: [] - # The amount of replicas to run the deployment with - replicas: 1 - # NodeSelector used to schedule the syncer - nodeSelector: {} - # Affinity to apply to the syncer deployment - affinity: {} - # Tolerations to apply to the syncer deployment - tolerations: [] - # Extra Labels for the syncer deployment - labels: {} - # Extra Annotations for the syncer deployment - annotations: {} - podAnnotations: {} - podLabels: {} - priorityClassName: "" - kubeConfigContextName: "my-vcluster" - # Security context configuration - securityContext: - allowPrivilegeEscalation: false - podSecurityContext: - runAsUser: 0 - runAsGroup: 0 - serviceAnnotations: {} - # Storage settings for the vcluster - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - -# Etcd settings -etcd: - image: registry.k8s.io/etcd:3.5.10-0 - imagePullPolicy: "" - # The amount of replicas to run - replicas: 1 - # NodeSelector used - nodeSelector: {} - # Affinity to apply - affinity: {} - # Tolerations to apply - tolerations: [] - # Extra Labels - labels: {} - # Extra Annotations - annotations: {} - podAnnotations: {} - podLabels: {} - resources: - requests: - cpu: 20m - memory: 150Mi - # Storage settings for the etcd - storage: - # If this is disabled, vcluster will use an emptyDir instead - # of a PersistentVolumeClaim - persistence: true - # Size of the persistent volume claim - size: 5Gi - # Optional StorageClass used for the pvc - # if empty default StorageClass defined in your host cluster will be used - #className: - priorityClassName: "" - securityContext: {} - serviceAnnotations: {} - autoDeletePersistentVolumeClaims: false - -# Kubernetes Controller Manager settings -controller: - image: registry.k8s.io/kube-controller-manager:v1.29.0 - imagePullPolicy: "" - -# Kubernetes Scheduler settings. Only enabled if sync.nodes.enableScheduler is true -scheduler: - image: registry.k8s.io/kube-scheduler:v1.29.0 - imagePullPolicy: "" - disabled: true - -# Kubernetes API Server settings -api: - image: registry.k8s.io/kube-apiserver:v1.29.0 - imagePullPolicy: "" - extraArgs: [] - -# Service account that should be used by the vcluster -serviceAccount: - create: true - # Optional name of the service account to use - # name: default - # Optional pull secrets - # imagePullSecrets: - # - name: my-pull-secret - -# Service account that should be used by the pods synced by vcluster -workloadServiceAccount: - # This is not supported in multi-namespace mode - annotations: {} - -# Roles & ClusterRoles for the vcluster -rbac: - clusterRole: - # You don't need to toggle this as necessary cluster roles are created based on the enabled syncers (.sync.*.enabled). - # This only makes sense to enable if you want to use the extraRules below. - create: false - # Extra Rules for the cluster role - extraRules: [] - role: - # Disable this only if you don't want vCluster to create a role. This will break most functionality if disabled. - create: true - # Extra Rules for the role - extraRules: [] - # all entries in excludedApiResources will be excluded from the Role created for vcluster - excludedApiResources: - # - pods/exec - -# Syncer service configurations -service: - type: ClusterIP - - # Optional configuration - # A list of IP addresses for which nodes in the cluster will also accept traffic for this service. - # These IPs are not managed by Kubernetes; e.g., an external load balancer. - externalIPs: [] - - # Optional configuration for LoadBalancer & NodePort service types - # Route external traffic to node-local or cluster-wide endpoints [ Local | Cluster ] - externalTrafficPolicy: "" - - # Optional configuration for LoadBalancer service type - # Specify IP of load balancer to be created - loadBalancerIP: "" - # CIDR block(s) for the service allowlist - loadBalancerSourceRanges: [] - # Set the loadBalancerClass if using an external load balancer controller - loadBalancerClass: "" - # Set loadBalancer specific annotations on the Kubernetes service - loadBalancerAnnotations: {} - -# Configure the ingress resource that allows you to access the vcluster -ingress: - # Enable ingress record generation - enabled: false - # Ingress path type - pathType: ImplementationSpecific - ingressClassName: "" - host: vcluster.local - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTPS - nginx.ingress.kubernetes.io/ssl-passthrough: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - # Ingress TLS configuration - tls: [] - # - secretName: tls-vcluster.local - # hosts: - # - vcluster.local - -# Set "enable" to true when running vcluster in an OpenShift host -# This will add an extra rule to the deployed role binding in order -# to manage service endpoints -openshift: - enable: false - -# If enabled will deploy the coredns configmap -coredns: - # If CoreDns is enabled - enabled: true - # Pro only feature - integrated: false - # only used if isolation is enabled - fallback: 8.8.8.8 - plugin: - enabled: false - config: [] - # example configuration for plugin syntax, will be documented in detail - # - record: - # fqdn: google.com - # target: - # mode: url - # url: google.co.in - # - record: - # service: my-namespace/my-svc # dns-test/nginx-svc - # target: - # mode: host - # service: dns-test/nginx-svc - # - record: - # service: my-namespace-lb/my-svc-lb - # target: - # mode: host - # service: dns-test-exposed-lb/nginx-svc-exposed-lb - # - record: - # service: my-ns-external-name/my-svc-external-name - # target: - # mode: host - # service: dns-test-external-name/nginx-svc-external-name - # - record: - # service: my-ns-in-vcluster/my-svc-vcluster - # target: - # mode: vcluster # can be tested only manually for now - # vcluster: test-vcluster-ns/test-vcluster - # service: dns-test-in-vcluster-ns/test-in-vcluster-service - # - record: - # service: my-ns-in-vcluster-mns/my-svc-mns - # target: - # mode: vcluster # can be tested only manually for now - # service: dns-test-in-vcluster-mns/test-in-vcluster-svc-mns - # vcluster: test-vcluster-ns-mns/test-vcluster-mns - # - record: - # service: my-self-vc-ns/my-self-vc-svc - # target: - # mode: self - # service: dns-test/nginx-svc - replicas: 1 - # The nodeSelector example below specifices that coredns should only be scheduled to nodes with the arm64 label - # nodeSelector: - # kubernetes.io/arch: arm64 - # image: my-core-dns-image:latest - # config: |- - # .:1053 { - # ... - # CoreDNS service configurations - service: - type: ClusterIP - # Configuration for LoadBalancer service type - externalIPs: [] - externalTrafficPolicy: "" - # Extra Annotations - annotations: {} - resources: - limits: - cpu: 1000m - memory: 170Mi - requests: - cpu: 3m - memory: 16Mi -# if below option is configured, it will override the coredns manifests with the following string -# manifests: |- -# apiVersion: ... -# ... - podAnnotations: {} - podLabels: {} - -# If enabled will deploy vcluster in an isolated mode with pod security -# standards, limit ranges and resource quotas -isolation: - enabled: false - namespace: null - - podSecurityStandard: baseline - - # If enabled will add node/proxy permission to the cluster role - # in isolation mode - nodeProxyPermission: - enabled: false - - resourceQuota: - enabled: true - quota: - requests.cpu: 10 - requests.memory: 20Gi - requests.storage: "100Gi" - requests.ephemeral-storage: 60Gi - limits.cpu: 20 - limits.memory: 40Gi - limits.ephemeral-storage: 160Gi - services.nodeports: 0 - services.loadbalancers: 1 - count/endpoints: 40 - count/pods: 20 - count/services: 20 - count/secrets: 100 - count/configmaps: 100 - count/persistentvolumeclaims: 20 - scopeSelector: - matchExpressions: - scopes: - - limitRange: - enabled: true - default: - ephemeral-storage: 8Gi - memory: 512Mi - cpu: "1" - defaultRequest: - ephemeral-storage: 3Gi - memory: 128Mi - cpu: 100m - - networkPolicy: - enabled: true - outgoingConnections: - ipBlock: - cidr: 0.0.0.0/0 - except: - - 100.64.0.0/10 - - 127.0.0.0/8 - - 10.0.0.0/8 - - 172.16.0.0/12 - - 192.168.0.0/16 - -# manifests to setup when initializing a vcluster -init: - manifests: |- - --- - # The contents of manifests-template will be templated using helm - # this allows you to use helm values inside, e.g.: {{ .Release.Name }} - manifestsTemplate: '' - helm: [] - # - bundle: - base64-encoded .tar.gz file content (optional - overrides chart.repo) - # chart: - # name: REQUIRED - # version: REQUIRED - # repo: (optional when bundle is used) - # username: (if required for repo) - # password: (if required for repo) - # insecure: boolean (if required for repo) - # release: - # name: REQUIRED - # namespace: REQUIRED - # timeout: number - # values: |- string YAML object - # foo: bar - # valuesTemplate: |- string YAML object - # foo: {{ .Release.Name }} - -multiNamespaceMode: - enabled: false - - -# list of {validating/mutating}webhooks that the syncer should proxy. -# This is a PRO only feature. -centralAdmission: - validatingWebhooks: [] - mutatingWebhooks: [] - -telemetry: - disabled: false - instanceCreator: "helm" - platformUserID: "" - platformInstanceID: "" - machineID: "" - diff --git a/cmd/vcluster/cmd/start.go b/cmd/vcluster/cmd/start.go index 6a8319961..382cf7a3d 100644 --- a/cmd/vcluster/cmd/start.go +++ b/cmd/vcluster/cmd/start.go @@ -6,8 +6,8 @@ import ( "os" "runtime/debug" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/leaderelection" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/plugin" "github.com/loft-sh/vcluster/pkg/pro" "github.com/loft-sh/vcluster/pkg/scheme" @@ -17,10 +17,17 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" ) +type StartOptions struct { + Config string + + SetValues []string +} + func NewStartCommand() *cobra.Command { - vClusterOptions := &options.VirtualClusterOptions{} + startOptions := &StartOptions{} cmd := &cobra.Command{ Use: "start", Short: "Execute the vcluster", @@ -40,52 +47,57 @@ func NewStartCommand() *cobra.Command { } }() + // parse vCluster config + vClusterConfig, err := config.ParseConfig(startOptions.Config, os.Getenv("VCLUSTER_NAME"), startOptions.SetValues) + if err != nil { + return err + } + // execute command - return ExecuteStart(cobraCmd.Context(), vClusterOptions) + return ExecuteStart(cobraCmd.Context(), vClusterConfig) }, } - options.AddFlags(cmd.Flags(), vClusterOptions) - pro.AddProFlags(cmd.Flags(), vClusterOptions) + cmd.Flags().StringVar(&startOptions.Config, "config", "/var/vcluster/config.yaml", "The path where to find the vCluster config to load") + + // Should only used for development + cmd.Flags().StringArrayVar(&startOptions.SetValues, "set", []string{}, "Set values for the config. E.g. --set 'exportKubeConfig.secret.name=my-name'") + + _ = cmd.Flags().MarkHidden("set") return cmd } -func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) error { - err := pro.ValidateProOptions(options) - if err != nil { - return err - } - - // set suffix - translate.VClusterName = options.Name - if translate.VClusterName == "" { - translate.VClusterName = options.DeprecatedSuffix - } - if translate.VClusterName == "" { - translate.VClusterName = "vcluster" - } +func ExecuteStart(ctx context.Context, vConfig *config.VirtualClusterConfig) error { + // set global vCluster name + translate.VClusterName = vConfig.Name // set service name - if options.ServiceName == "" { - options.ServiceName = translate.VClusterName + if vConfig.ControlPlane.Advanced.WorkloadServiceAccount.Name == "" { + vConfig.ControlPlane.Advanced.WorkloadServiceAccount.Name = "vc-workload-" + vConfig.Name } // get current namespace - controlPlaneConfig, controlPlaneNamespace, controlPlaneService, workloadConfig, workloadNamespace, workloadService, err := pro.GetRemoteClient(options) + controlPlaneConfig, controlPlaneNamespace, controlPlaneService, workloadConfig, workloadNamespace, workloadService, err := pro.GetRemoteClient(vConfig) if err != nil { return err } - options.ServiceName = workloadService + vConfig.ServiceName = workloadService err = os.Setenv("NAMESPACE", workloadNamespace) if err != nil { return fmt.Errorf("set NAMESPACE env var: %w", err) } + // set target namespace + vConfig.TargetNamespace = workloadNamespace + if vConfig.Experimental.SyncSettings.TargetNamespace != "" { + vConfig.TargetNamespace = vConfig.Experimental.SyncSettings.TargetNamespace + } + // init telemetry - telemetry.Collector.Init(controlPlaneConfig, controlPlaneNamespace, options) + telemetry.Collector.Init(controlPlaneConfig, controlPlaneNamespace, vConfig) // initialize feature gate from environment - err = pro.LicenseInit(ctx, controlPlaneConfig, controlPlaneNamespace, options.ProOptions.ProLicenseSecret) + err = pro.LicenseInit(ctx, controlPlaneConfig, controlPlaneNamespace, vConfig) if err != nil { return fmt.Errorf("init license: %w", err) } @@ -94,10 +106,6 @@ func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) e plugin.DefaultManager.SetProFeatures(pro.LicenseFeatures()) // get host cluster config and tweak rate-limiting configuration - workloadClient, err := kubernetes.NewForConfig(workloadConfig) - if err != nil { - return err - } controlPlaneClient, err := kubernetes.NewForConfig(controlPlaneConfig) if err != nil { return err @@ -106,12 +114,10 @@ func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) e // check if we should create certs err = setup.Initialize( ctx, - workloadClient, controlPlaneClient, - workloadNamespace, controlPlaneNamespace, translate.VClusterName, - options, + vConfig, ) if err != nil { return fmt.Errorf("initialize: %w", err) @@ -120,7 +126,7 @@ func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) e // build controller context controllerCtx, err := setup.NewControllerContext( ctx, - options, + vConfig, workloadNamespace, workloadConfig, scheme.Scheme, @@ -140,8 +146,15 @@ func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) e return fmt.Errorf("start proxy: %w", err) } - // start integrated coredns - if controllerCtx.Options.ProOptions.IntegratedCoredns { + // should start embedded coredns? + if vConfig.ControlPlane.CoreDNS.Embedded { + // write vCluster kubeconfig to /data/vcluster/admin.conf + err = clientcmd.WriteToFile(*controllerCtx.VirtualRawConfig, "/data/vcluster/admin.conf") + if err != nil { + return fmt.Errorf("write vCluster kube config for embedded coredns: %w", err) + } + + // start embedded coredns err = pro.StartIntegratedCoreDNS(controllerCtx) if err != nil { return fmt.Errorf("start integrated core dns: %w", err) @@ -160,9 +173,9 @@ func ExecuteStart(ctx context.Context, options *options.VirtualClusterOptions) e return nil } -func StartLeaderElection(ctx *options.ControllerContext, startLeading func() error) error { +func StartLeaderElection(ctx *config.ControllerContext, startLeading func() error) error { var err error - if ctx.Options.LeaderElect { + if ctx.Config.ControlPlane.StatefulSet.HighAvailability.Replicas > 1 { err = leaderelection.StartLeaderElection(ctx, scheme.Scheme, func() error { return startLeading() }) diff --git a/cmd/vclusterctl/cmd/app/create/pro.go b/cmd/vclusterctl/cmd/app/create/pro.go index 91a7392c7..1993d0d82 100644 --- a/cmd/vclusterctl/cmd/app/create/pro.go +++ b/cmd/vclusterctl/cmd/app/create/pro.go @@ -21,7 +21,7 @@ import ( "github.com/loft-sh/loftctl/v3/pkg/config" "github.com/loft-sh/loftctl/v3/pkg/vcluster" "github.com/loft-sh/log" - "github.com/loft-sh/vcluster-values/values" + vclusterconfig "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/procli" "github.com/loft-sh/vcluster/pkg/strvals" "github.com/loft-sh/vcluster/pkg/telemetry" @@ -492,9 +492,6 @@ func validateTemplateOptions(options *Options) error { if len(options.Values) > 0 { return fmt.Errorf("cannot use --values because the vcluster is using a template. Please use --params instead") } - if options.Isolate { - return fmt.Errorf("cannot use --isolate because the vcluster is using a template") - } if options.KubernetesVersion != "" { return fmt.Errorf("cannot use --kubernetes-version because the vcluster is using a template") } @@ -521,7 +518,7 @@ func mergeValues(proClient procli.Client, options *Options, log log.Logger) (str return "", err } logger := logr.New(log.LogrLogSink()) - chartValues, err := values.GetDefaultReleaseValues(chartOptions, logger) + chartValues, err := vclusterconfig.GetExtraValues(chartOptions, logger) if err != nil { return "", err } @@ -532,13 +529,6 @@ func mergeValues(proClient procli.Client, options *Options, log log.Logger) (str return "", err } - // set integrated to true - if outValues["coredns"] == nil { - outValues["coredns"] = map[string]interface{}{ - "integrated": true, - } - } - // merge values for _, valuesFile := range options.Values { out, err := os.ReadFile(valuesFile) @@ -581,7 +571,7 @@ func parseString(str string) (map[string]interface{}, error) { return out, nil } -func toChartOptions(proClient procli.Client, options *Options, log log.Logger) (*values.ChartOptions, error) { +func toChartOptions(proClient procli.Client, options *Options, log log.Logger) (*vclusterconfig.ExtraValuesOptions, error) { if !util.Contains(options.Distro, AllowedDistros) { return nil, fmt.Errorf("unsupported distro %s, please select one of: %s", options.Distro, strings.Join(AllowedDistros, ", ")) } @@ -590,7 +580,7 @@ func toChartOptions(proClient procli.Client, options *Options, log log.Logger) ( options.ChartName += "-" + options.Distro } - version := values.Version{} + version := vclusterconfig.KubernetesVersion{} if options.KubernetesVersion != "" { if options.KubernetesVersion[0] != 'v' { options.KubernetesVersion = "v" + options.KubernetesVersion @@ -605,7 +595,7 @@ func toChartOptions(proClient procli.Client, options *Options, log log.Logger) ( log.Warnf("currently we only support major.minor version (%s) and not the patch version (%s)", majorMinorVer, options.KubernetesVersion) } - parsedVersion, err := values.ParseKubernetesVersionInfo(majorMinorVer) + parsedVersion, err := vclusterconfig.ParseKubernetesVersionInfo(majorMinorVer) if err != nil { return nil, err } @@ -619,12 +609,8 @@ func toChartOptions(proClient procli.Client, options *Options, log log.Logger) ( options.ChartVersion = "" } - return &values.ChartOptions{ - ChartName: options.ChartName, - ChartRepo: options.ChartRepo, - ChartVersion: options.ChartVersion, - DisableIngressSync: options.DisableIngressSync, - Isolate: options.Isolate, + return &vclusterconfig.ExtraValuesOptions{ + Distro: options.Distro, KubernetesVersion: version, DisableTelemetry: cliconfig.GetConfig(log).TelemetryDisabled, InstanceCreatorType: "vclusterctl", diff --git a/cmd/vclusterctl/cmd/app/create/types.go b/cmd/vclusterctl/cmd/app/create/types.go index 4556b7e03..c8ad09c1c 100644 --- a/cmd/vclusterctl/cmd/app/create/types.go +++ b/cmd/vclusterctl/cmd/app/create/types.go @@ -8,22 +8,18 @@ type Options struct { ChartRepo string LocalChartDir string Distro string - CIDR string Values []string SetValues []string - DeprecatedExtraValues []string KubernetesVersion string - CreateNamespace bool - DisableIngressSync bool - UpdateCurrent bool - Expose bool - ExposeLocal bool + CreateNamespace bool + UpdateCurrent bool + Expose bool + ExposeLocal bool Connect bool Upgrade bool - Isolate bool // Pro Project string diff --git a/cmd/vclusterctl/cmd/connect.go b/cmd/vclusterctl/cmd/connect.go index 9b1fa4f7c..c770d4918 100644 --- a/cmd/vclusterctl/cmd/connect.go +++ b/cmd/vclusterctl/cmd/connect.go @@ -596,7 +596,7 @@ func (cmd *ConnectCmd) setServerIfExposed(ctx context.Context, vClusterName stri err := wait.PollUntilContextTimeout(ctx, time.Second*2, time.Minute*5, true, func(ctx context.Context) (done bool, err error) { // first check for load balancer service, look for the other service if it's not there loadBalancerMissing := false - service, err := cmd.kubeClient.CoreV1().Services(cmd.Namespace).Get(ctx, translate.GetLoadBalancerSVCName(vClusterName), metav1.GetOptions{}) + service, err := cmd.kubeClient.CoreV1().Services(cmd.Namespace).Get(ctx, vClusterName, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { loadBalancerMissing = true diff --git a/cmd/vclusterctl/cmd/create.go b/cmd/vclusterctl/cmd/create.go index 7dbfad7c3..5d819df2e 100644 --- a/cmd/vclusterctl/cmd/create.go +++ b/cmd/vclusterctl/cmd/create.go @@ -16,6 +16,7 @@ import ( "github.com/loft-sh/log/terminal" "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd/app/localkubernetes" "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd/find" + "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/embed" "github.com/loft-sh/vcluster/pkg/procli" "github.com/loft-sh/vcluster/pkg/util/cliconfig" @@ -25,11 +26,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" loftctlUtil "github.com/loft-sh/loftctl/v3/pkg/util" - "github.com/loft-sh/vcluster-values/values" "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd/app/create" "github.com/loft-sh/vcluster/pkg/upgrade" "github.com/loft-sh/vcluster/pkg/util" - "github.com/loft-sh/vcluster/pkg/util/servicecidr" "golang.org/x/mod/semver" "github.com/loft-sh/log" @@ -96,8 +95,6 @@ vcluster create test --namespace test cobraCmd.Flags().StringVar(&cmd.KubernetesVersion, "kubernetes-version", "", "The kubernetes version to use (e.g. v1.20). Patch versions are not supported") cobraCmd.Flags().StringArrayVarP(&cmd.Values, "values", "f", []string{}, "Path where to load extra helm values from") cobraCmd.Flags().StringArrayVar(&cmd.SetValues, "set", []string{}, "Set values for helm. E.g. --set 'persistence.enabled=true'") - cobraCmd.Flags().StringSliceVar(&cmd.DeprecatedExtraValues, "extra-values", []string{}, "DEPRECATED: use --values instead") - cobraCmd.Flags().BoolVar(&cmd.Isolate, "isolate", false, "If true vcluster and its workloads will run in an isolated environment") cobraCmd.Flags().BoolVar(&cmd.CreateNamespace, "create-namespace", true, "If true the namespace will be created if it does not exist") cobraCmd.Flags().BoolVar(&cmd.UpdateCurrent, "update-current", true, "If true updates the current kube config") @@ -120,11 +117,9 @@ vcluster create test --namespace test // hidden / deprecated cobraCmd.Flags().StringVar(&cmd.LocalChartDir, "local-chart-dir", "", "The virtual cluster local chart dir to use") - cobraCmd.Flags().BoolVar(&cmd.DisableIngressSync, "disable-ingress-sync", false, "DEPRECATED: use --set 'sync.ingresses.enabled=false'") cobraCmd.Flags().BoolVar(&cmd.ExposeLocal, "expose-local", true, "If true and a local Kubernetes distro is detected, will deploy vcluster with a NodePort service. Will be set to false and the passed value will be ignored if --expose is set to true.") _ = cobraCmd.Flags().MarkHidden("local-chart-dir") - _ = cobraCmd.Flags().MarkHidden("disable-ingress-sync") _ = cobraCmd.Flags().MarkHidden("expose-local") return cobraCmd } @@ -133,8 +128,6 @@ var loginText = "\nPlease run:\n * 'vcluster login' to connect to an existing vC // Run executes the functionality func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { - cmd.Values = append(cmd.Values, cmd.DeprecatedExtraValues...) - // check if we should create a pro cluster if !cmd.DisablePro { proClient, err := procli.CreateProClient() @@ -205,7 +198,7 @@ func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { return err } logger := logr.New(cmd.log.LogrLogSink()) - chartValues, err := values.GetDefaultReleaseValues(chartOptions, logger) + chartValues, err := config.GetExtraValues(chartOptions, logger) if err != nil { return err } @@ -391,6 +384,7 @@ func (cmd *CreateCmd) deployChart(ctx context.Context, vClusterName, chartValues Values: chartValues, ValuesFiles: cmd.Values, SetValues: cmd.SetValues, + Debug: cmd.Debug, }) if err != nil { return err @@ -399,7 +393,7 @@ func (cmd *CreateCmd) deployChart(ctx context.Context, vClusterName, chartValues return nil } -func (cmd *CreateCmd) ToChartOptions(kubernetesVersion *version.Info, log log.Logger) (*values.ChartOptions, error) { +func (cmd *CreateCmd) ToChartOptions(kubernetesVersion *version.Info, log log.Logger) (*config.ExtraValuesOptions, error) { if !util.Contains(cmd.Distro, create.AllowedDistros) { return nil, fmt.Errorf("unsupported distro %s, please select one of: %s", cmd.Distro, strings.Join(create.AllowedDistros, ", ")) } @@ -408,13 +402,6 @@ func (cmd *CreateCmd) ToChartOptions(kubernetesVersion *version.Info, log log.Lo cmd.ChartName += "-" + cmd.Distro } - // check if we're running in isolated mode - if cmd.Isolate { - // In this case, default the ExposeLocal variable to false - // as it will always fail creating a vcluster in isolated mode - cmd.ExposeLocal = false - } - // check if we should create with node port clusterType := localkubernetes.DetectClusterType(&cmd.rawConfig) if cmd.ExposeLocal && clusterType.LocalKubernetes() { @@ -422,17 +409,12 @@ func (cmd *CreateCmd) ToChartOptions(kubernetesVersion *version.Info, log log.Lo cmd.localCluster = true } - return &values.ChartOptions{ - ChartName: cmd.ChartName, - ChartRepo: cmd.ChartRepo, - ChartVersion: cmd.ChartVersion, - CIDR: cmd.CIDR, - DisableIngressSync: cmd.DisableIngressSync, - Expose: cmd.Expose, - SyncNodes: cmd.localCluster, - NodePort: cmd.localCluster, - Isolate: cmd.Isolate, - KubernetesVersion: values.Version{ + return &config.ExtraValuesOptions{ + Distro: cmd.Distro, + Expose: cmd.Expose, + SyncNodes: cmd.localCluster, + NodePort: cmd.localCluster, + KubernetesVersion: config.KubernetesVersion{ Major: kubernetesVersion.Major, Minor: kubernetesVersion.Minor, }, @@ -514,19 +496,6 @@ func (cmd *CreateCmd) prepare(ctx context.Context, vClusterName string) error { return err } - // get service cidr - if cmd.CIDR == "" { - cidr, warning := servicecidr.GetServiceCIDR(ctx, cmd.kubeClient, cmd.Namespace) - if warning != "" { - cmd.log.Debug(warning) - } - if cmd.Distro == "k0s" { - // there is currently a problem with dualstack when we use k0s - cidr = strings.Split(cidr, ",")[0] - } - cmd.CIDR = cidr - } - return nil } @@ -612,7 +581,7 @@ func (cmd *CreateCmd) getKubernetesVersion() (*version.Info, error) { cmd.log.Warnf("currently we only support major.minor version (%s) and not the patch version (%s)", majorMinorVer, cmd.KubernetesVersion) } - parsedVersion, err := values.ParseKubernetesVersionInfo(majorMinorVer) + parsedVersion, err := config.ParseKubernetesVersionInfo(majorMinorVer) if err != nil { return nil, err } diff --git a/config/README.md b/config/README.md new file mode 100644 index 000000000..5070dd4e7 --- /dev/null +++ b/config/README.md @@ -0,0 +1 @@ +This file will get synced to [github.com/loft-sh/vcluster-config](https://github.com/loft-sh/vcluster-config). Please do not add any external dependencies to the code here. diff --git a/config/config.go b/config/config.go new file mode 100644 index 000000000..bb1a0d548 --- /dev/null +++ b/config/config.go @@ -0,0 +1,1345 @@ +package config + +import ( + "regexp" +) + +// Config is the vCluster config. This struct describes valid helm values for vCluster as well as configuration used by the vCluster binary itself. +type Config struct { + // ExportKubeConfig describes how vCluster should export the vCluster kube config + ExportKubeConfig ExportKubeConfig `json:"exportKubeConfig,omitempty"` + + // Sync describes how to sync resources from the vCluster to host cluster and back + Sync Sync `json:"sync,omitempty"` + + // Networking are networking options related to the vCluster + Networking Networking `json:"networking,omitempty"` + + // Policies defines policies to enforce for the vCluster deployment as well as within the vCluster + Policies Policies `json:"policies,omitempty"` + + // Observability holds options to proxy metrics from the host cluster into the vCluster + Observability Observability `json:"observability,omitempty"` + + // ControlPlane holds options how to configure the vCluster control-plane + ControlPlane ControlPlane `json:"controlPlane,omitempty"` + + // RBAC are role based access control options for the vCluster + RBAC RBAC `json:"rbac,omitempty"` + + // Plugins define what vCluster plugins to load. + Plugins map[string]Plugins `json:"plugins,omitempty"` + + // Platform holds options how vCluster should connect to vCluster platform. + Platform Platform `json:"platform,omitempty"` + + // Experimental are alpha features for vCluster. Configuration here might change, so be careful with this. + Experimental Experimental `json:"experimental,omitempty"` + + // Telemetry is the configuration related to telemetry gathered about vCluster usage. + Telemetry Telemetry `json:"telemetry,omitempty"` + + // ServiceCIDR holds the service cidr for the vCluster. Please do not use that option anymore. + ServiceCIDR string `json:"serviceCIDR,omitempty"` + + // Pro specifies if vCluster pro should be used. This is automatically inferred in newer versions. Please do not use that option anymore. + Pro bool `json:"pro,omitempty"` + + // Plugin specifies what vCluster plugins to enable. Please use "plugins" instead. Please do not use that option anymore. + Plugin map[string]Plugin `json:"plugin,omitempty"` +} + +// ExportKubeConfig describes how vCluster should export the vCluster kube config +type ExportKubeConfig struct { + // Context is the name of the context within the generated kube config to use. + Context string `json:"context"` + + // Server can be used to override the default https://localhost:8443 and specify a custom hostname for the + // generated kube-config. + Server string `json:"server"` + + // Secret defines in which secret in the host cluster the generated kube-config should be stored. + // If this is not defined, vCluster will only create it at `vc-NAME`. If another name is specified here + // vCluster will also create the config in this other secret. + Secret ExportKubeConfigSecretReference `json:"secret,omitempty"` +} + +// ExportKubeConfigSecretReference defines in which secret in the host cluster the generated kube-config should be stored. +// If this is not defined, vCluster will only create it at `vc-NAME`. If another name is specified here +// vCluster will also create the config in this other secret. +type ExportKubeConfigSecretReference struct { + // Name is the name of the secret where the kube config should get stored. + Name string `json:"name,omitempty"` + + // Namespace defines the namespace where the kube config secret should get stored. If this is not equal to the namespace + // where the vCluster is deployed, you need to make sure vCluster has access to this other namespace. + Namespace string `json:"namespace,omitempty"` +} + +type Sync struct { + // ToHost configures what resources should get synced from the vCluster to the host cluster. + ToHost SyncToHost `json:"toHost,omitempty"` + + // FromHost configures what resources should get purely synced from the host cluster to the vCluster. + FromHost SyncFromHost `json:"fromHost,omitempty"` +} + +type SyncToHost struct { + // Pods defines if pods created within the vCluster should get synced to the host cluster. + Pods SyncPods `json:"pods,omitempty"` + // Secrets defines if secrets created within the vCluster should get synced to the host cluster. + Secrets SyncAllResource `json:"secrets,omitempty"` + // ConfigMaps defines if config maps created within the vCluster should get synced to the host cluster. + ConfigMaps SyncAllResource `json:"configMaps,omitempty"` + // Ingresses defines if ingresses created within the vCluster should get synced to the host cluster. + Ingresses EnableSwitch `json:"ingresses,omitempty"` + // Services defines if services created within the vCluster should get synced to the host cluster. + Services EnableSwitch `json:"services,omitempty"` + // Endpoints defines if endpoints created within the vCluster should get synced to the host cluster. + Endpoints EnableSwitch `json:"endpoints,omitempty"` + // NetworkPolicies defines if network policies created within the vCluster should get synced to the host cluster. + NetworkPolicies EnableSwitch `json:"networkPolicies,omitempty"` + // PersistentVolumeClaims defines if persistent volume claims created within the vCluster should get synced to the host cluster. + PersistentVolumeClaims EnableSwitch `json:"persistentVolumeClaims,omitempty"` + // PersistentVolumes defines if persistent volumes created within the vCluster should get synced to the host cluster. + PersistentVolumes EnableSwitch `json:"persistentVolumes,omitempty"` + // VolumeSnapshots defines if volume snapshots created within the vCluster should get synced to the host cluster. + VolumeSnapshots EnableSwitch `json:"volumeSnapshots,omitempty"` + // StorageClasses defines if storage classes created within the vCluster should get synced to the host cluster. + StorageClasses EnableSwitch `json:"storageClasses,omitempty"` + // ServiceAccounts defines if service accounts created within the vCluster should get synced to the host cluster. + ServiceAccounts EnableSwitch `json:"serviceAccounts,omitempty"` + // PodDisruptionBudgets defines if pod disruption budgets created within the vCluster should get synced to the host cluster. + PodDisruptionBudgets EnableSwitch `json:"podDisruptionBudgets,omitempty"` + // PriorityClasses defines if priority classes created within the vCluster should get synced to the host cluster. + PriorityClasses EnableSwitch `json:"priorityClasses,omitempty"` +} + +type SyncFromHost struct { + // Nodes defines if nodes should get synced from the host cluster to the vCluster, but not back. + Nodes SyncNodes `json:"nodes,omitempty"` + // Events defines if events should get synced from the host cluster to the vCluster, but not back. + Events EnableSwitch `json:"events,omitempty"` + // IngressClasses defines if ingress classes should get synced from the host cluster to the vCluster, but not back. + IngressClasses EnableSwitch `json:"ingressClasses,omitempty"` + // StorageClasses defines if storage classes should get synced from the host cluster to the vCluster, but not back. + StorageClasses EnableSwitch `json:"storageClasses,omitempty"` + // CSINodes defines if csi nodes should get synced from the host cluster to the vCluster, but not back. + CSINodes EnableSwitch `json:"csiNodes,omitempty"` + // CSIDrivers defines if csi drivers should get synced from the host cluster to the vCluster, but not back. + CSIDrivers EnableSwitch `json:"csiDrivers,omitempty"` + // CSIStorageCapacities defines if csi storage capacities should get synced from the host cluster to the vCluster, but not back. + CSIStorageCapacities EnableSwitch `json:"csiStorageCapacities,omitempty"` +} + +type EnableSwitch struct { + // Enabled defines if this option should be enabled. + Enabled bool `json:"enabled,omitempty"` +} + +type SyncAllResource struct { + // Enabled defines if this option should be enabled. + Enabled bool `json:"enabled,omitempty"` + + // All defines if all resources of that type should get synced or only the necessary ones that are needed. + All bool `json:"all,omitempty"` +} + +type SyncPods struct { + // Enabled defines if pod syncing should be enabled. + Enabled bool `json:"enabled,omitempty"` + + // TranslateImage maps an image to another image that should be used instead. For example this can be used to rewrite + // a certain image that is used within the vCluster to be another image on the host cluster + TranslateImage map[string]string `json:"translateImage,omitempty"` + + // EnforceTolerations will add the specified tolerations to all pods synced by the vCluster. + EnforceTolerations []string `json:"enforceTolerations,omitempty"` + + // UseSecretsForSATokens will use secrets to save the generated service account tokens by vCluster instead of using a + // pod annotation. + UseSecretsForSATokens bool `json:"useSecretsForSATokens,omitempty"` + + // RewriteHosts is a special option needed to rewrite statefulset containers to allow the correct FQDN. vCluster will add + // a small container to each stateful set pod that will initially rewrite the /etc/hosts file to match the FQDN expected by + // the vCluster. + RewriteHosts SyncRewriteHosts `json:"rewriteHosts,omitempty"` +} + +type SyncRewriteHosts struct { + // Enabled specifies if rewriting stateful set pods should be enabled. + Enabled bool `json:"enabled,omitempty"` + + // InitContainerImage is the image vCluster should use to rewrite this FQDN. + InitContainerImage string `json:"initContainerImage,omitempty"` +} + +type SyncNodes struct { + // Enabled specifies if syncing real nodes should be enabled. If this is disabled, vCluster will create fake nodes instead. + Enabled bool `json:"enabled,omitempty"` + + // SyncBackChanges enables syncing labels and taints from the vCluster to the host cluster. If this is enabled someone within the vCluster will be able to change the labels and taints of the host cluster node. + SyncBackChanges bool `json:"syncBackChanges,omitempty"` + + // ClearImageStatus will erase the image status when syncing a node. This allows to hide images that are pulled by the node. + ClearImageStatus bool `json:"clearImageStatus,omitempty"` + + // Selector can be used to define more granular what nodes should get synced from the host cluster to the vCluster. + Selector SyncNodeSelector `json:"selector,omitempty"` +} + +type SyncNodeSelector struct { + // All specifies if all nodes should get synced by vCluster from the host to the vCluster or only the ones where pods are assigned to. + All bool `json:"all,omitempty"` + + // Labels are the node labels used to sync nodes from host cluster to vCluster. This will also set the node selector when syncing a pod from vCluster to host cluster to the same value. + Labels map[string]string `json:"labels,omitempty"` +} + +type Observability struct { + // Metrics allows to proxy metrics server apis from host to vCluster. + Metrics ObservabilityMetrics `json:"metrics,omitempty"` +} + +type ServiceMonitor struct { + // Enabled configures if helm should create the service monitor. + Enabled bool `json:"enabled,omitempty"` + + // Labels are the extra labels to add to the service monitor. + Labels map[string]string `json:"labels,omitempty"` + + // Annotations are the extra annotations to add to the service monitor. + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ObservabilityMetrics struct { + // Proxy holds the configuration what metrics-server apis should get proxied. + Proxy MetricsProxy `json:"proxy,omitempty"` +} + +type MetricsProxy struct { + // Nodes defines if metrics-server nodes api should get proxied from host to vCluster. + Nodes bool `json:"nodes,omitempty"` + + // Pods defines if metrics-server pods api should get proxied from host to vCluster. + Pods bool `json:"pods,omitempty"` +} + +type Networking struct { + // ReplicateServices allows replicating services from the host within the vCluster or the other way around. + ReplicateServices ReplicateServices `json:"replicateServices,omitempty"` + + // ResolveDNS allows to define extra DNS rules. This only works if embedded coredns is configured. + ResolveDNS []ResolveDNS `json:"resolveDNS,omitempty"` + + // Advanced holds advanced network options. + Advanced NetworkingAdvanced `json:"advanced,omitempty"` +} + +type ReplicateServices struct { + // ToHost defines the services that should get synced from vCluster to the host cluster. If services are + // synced to a different namespace than the vCluster is in, additional permissions for the other namespace + // are required. + ToHost []ServiceMapping `json:"toHost,omitempty"` + + // FromHost defines the services that should get synced from the host to the vCluster. + FromHost []ServiceMapping `json:"fromHost,omitempty"` +} + +type ServiceMapping struct { + // From is the service that should get synced. Can be either in the form name or namespace/name. + From string `json:"from,omitempty"` + // To is the target service that it should get synced to. Can be either in the form name or namespace/name. + To string `json:"to,omitempty"` +} + +type ResolveDNS struct { + Hostname string `json:"hostname"` + Service string `json:"service"` + Namespace string `json:"namespace"` + + Target ResolveDNSTarget `json:"target,omitempty"` +} + +type ResolveDNSTarget struct { + Hostname string `json:"hostname,omitempty"` + IP string `json:"ip,omitempty"` + + // HostService to target, format is hostNamespace/hostService + HostService string `json:"hostService,omitempty"` + // HostNamespace to target + HostNamespace string `json:"hostNamespace,omitempty"` + // VClusterService format is hostNamespace/vClusterName/vClusterNamespace/vClusterService + VClusterService string `json:"vClusterService,omitempty"` +} + +type NetworkingAdvanced struct { + // ClusterDomain is the Kubernetes cluster domain to use within the vCluster. + ClusterDomain string `json:"clusterDomain,omitempty"` + + // FallbackHostCluster allows to fallback dns to the host cluster. This is useful if you want to reach host services without + // any other modification. You will need to provide a namespace for the service, e.g. my-other-service.my-other-namespace + FallbackHostCluster bool `json:"fallbackHostCluster,omitempty"` + + // ProxyKubelets allows rewriting certain metrics and stats from the Kubelet to "fake" this for applications such as + // prometheus or other node exporters. + ProxyKubelets NetworkProxyKubelets `json:"proxyKubelets,omitempty"` +} + +type NetworkProxyKubelets struct { + // ByHostname will add a special vCluster hostname to the nodes where the node can be reached at. This doesn't work + // for all applications, e.g. prometheus requires a node ip. + ByHostname bool `json:"byHostname,omitempty"` + + // ByIP will create a separate service in the host cluster for every node that will point to vCluster and will be used to + // route traffic. + ByIP bool `json:"byIP,omitempty"` +} + +type Plugin struct { + Plugins `json:",inline"` + + // Version is the plugin version, this is only needed for legacy plugins. + Version string `json:"version,omitempty"` + Env []interface{} `json:"env,omitempty"` + EnvFrom []interface{} `json:"envFrom,omitempty"` + Lifecycle map[string]interface{} `json:"lifecycle,omitempty"` + LivenessProbe map[string]interface{} `json:"livenessProbe,omitempty"` + ReadinessProbe map[string]interface{} `json:"readinessProbe,omitempty"` + StartupProbe map[string]interface{} `json:"startupProbe,omitempty"` + WorkingDir string `json:"workingDir,omitempty"` + Optional bool `json:"optional,omitempty"` +} + +type Plugins struct { + // Name is the name of the init-container and NOT the plugin name + Name string `json:"name,omitempty"` + // Image is the container image that should be used for the plugin + Image string `json:"image,omitempty"` + // ImagePullPolicy is the pull policy to use for the container image + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // Config is the plugin config to use. This can be arbitrary config used for the plugin. + Config map[string]interface{} `json:"config,omitempty"` + // RBAC holds additional rbac configuration for the plugin + RBAC PluginsRBAC `json:"rbac,omitempty"` + + // Command is the command that should be used for the init container + Command []string `json:"command,omitempty"` + // Args are the arguments that should be used for the init container + Args []string `json:"args,omitempty"` + // SecurityContext is the container security context used for the init container + SecurityContext map[string]interface{} `json:"securityContext,omitempty"` + // Resources are the container resources used for the init container + Resources map[string]interface{} `json:"resources,omitempty"` + // VolumeMounts are extra volume mounts for the init container + VolumeMounts []interface{} `json:"volumeMounts,omitempty"` +} + +type PluginsRBAC struct { + // Role holds extra vCluster role permissions for the plugin + Role PluginsExtraRules `json:"role,omitempty"` + // ClusterRole holds extra vCluster cluster role permissions required for the plugin + ClusterRole PluginsExtraRules `json:"clusterRole,omitempty"` +} + +type PluginsExtraRules struct { + // ExtraRules are extra rbac permissions roles that will be added to role or cluster role + ExtraRules []RBACPolicyRule `json:"extraRules,omitempty"` +} + +type RBACPolicyRule struct { + // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. + Verbs []string `json:"verbs"` + + // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of + // the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. + APIGroups []string `json:"apiGroups,omitempty"` + // Resources is a list of resources this rule applies to. '*' represents all resources. + Resources []string `json:"resources,omitempty"` + // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + ResourceNames []string `json:"resourceNames,omitempty"` + + // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path + // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. + // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + NonResourceURLs []string `json:"nonResourceURLs,omitempty"` +} + +type ControlPlane struct { + // Distro holds vCluster related distro options. + Distro Distro `json:"distro,omitempty"` + // BackingStore defines which backing store to use for vCluster. If not defined will fallback to the default distro backing store. + BackingStore BackingStore `json:"backingStore,omitempty"` + // CoreDNS defines everything coredns related. + CoreDNS CoreDNS `json:"coredns,omitempty"` + // Proxy defines options for the vCluster control plane proxy that is used to do authentication and intercept requests. + Proxy ControlPlaneProxy `json:"proxy,omitempty"` + // HostPathMapper defines if vCluster should rewrite host paths. + HostPathMapper HostPathMapper `json:"hostPathMapper,omitempty"` + // Ingress defines options for the vCluster ingress deployed by helm. + Ingress ControlPlaneIngress `json:"ingress,omitempty"` + // Service defines options for the vCluster service deployed by helm. + Service ControlPlaneService `json:"service,omitempty"` + // StatefulSet defines options for the vCluster statefulSet deployed by helm. + StatefulSet ControlPlaneStatefulSet `json:"statefulSet,omitempty"` + // ServiceMonitor can be used to automatically create a service monitor for vCluster deployment itself. + ServiceMonitor ServiceMonitor `json:"serviceMonitor,omitempty"` + // Advanced holds additional configuration for the vCluster control plane. + Advanced ControlPlaneAdvanced `json:"advanced,omitempty"` +} + +type ControlPlaneStatefulSet struct { + // HighAvailability holds options related to high availability. + HighAvailability ControlPlaneHighAvailability `json:"highAvailability,omitempty"` + // Resources are the resource requests and limits for the statefulSet container. + Resources Resources `json:"resources,omitempty"` + // Scheduling holds options related to scheduling. + Scheduling ControlPlaneScheduling `json:"scheduling,omitempty"` + // Security defines pod or container security context. + Security ControlPlaneSecurity `json:"security,omitempty"` + // Probes enables or disables the main container probes. + Probes ControlPlaneProbes `json:"probes,omitempty"` + // Persistence defines options around persistence for the statefulSet. + Persistence ControlPlanePersistence `json:"persistence,omitempty"` + + LabelsAndAnnotations `json:",inline"` + + // Pods are additional labels or annotations for the statefulSet pod. + Pods LabelsAndAnnotations `json:"pods,omitempty"` + + // Image is the image for the controlPlane statefulSet container + Image Image `json:"image,omitempty"` + // ImagePullPolicy is the policy how to pull the image. + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // WorkingDir specifies in what folder the main process should get started. + WorkingDir string `json:"workingDir,omitempty"` + // Command allows you to override the main command. + Command []string `json:"command,omitempty"` + // Args allows you to override the main arguments. + Args []string `json:"args,omitempty"` + // Env are additional environment variables for the statefulSet container. + Env []map[string]interface{} `json:"env,omitempty"` +} + +type Distro struct { + // K3S holds k3s relevant configuration. + K3S DistroK3s `json:"k3s,omitempty"` + // K0S holds k0s relevant configuration. + K0S DistroK0s `json:"k0s,omitempty"` + // K8S holds k8s relevant configuration. + K8S DistroK8s `json:"k8s,omitempty"` + // EKS holds eks relevant configuration. + EKS DistroK8s `json:"eks,omitempty"` +} + +type DistroK3s struct { + // Enabled specifies if the k3s distro should be enabled. Only one distro can be enabled at the same time. + Enabled bool `json:"enabled,omitempty"` + // Token is the k3s token to use. If empty, vCluster will choose one. + Token string `json:"token,omitempty"` + + DistroCommon `json:",inline"` + DistroContainer `json:",inline"` +} + +type DistroK8s struct { + // Enabled specifies if the k8s distro should be enabled. Only one distro can be enabled at the same time. + Enabled bool `json:"enabled,omitempty"` + + // APIServer holds configuration specific to starting the api server. + APIServer DistroContainerDisabled `json:"apiServer,omitempty"` + // ControllerManager holds configuration specific to starting the scheduler. + ControllerManager DistroContainerDisabled `json:"controllerManager,omitempty"` + // Scheduler holds configuration specific to starting the scheduler. + Scheduler DistroContainer `json:"scheduler,omitempty"` + + DistroCommon `json:",inline"` +} + +type DistroK0s struct { + // Enabled specifies if the k0s distro should be enabled. Only one distro can be enabled at the same time. + Enabled bool `json:"enabled,omitempty"` + // Config allows you to override the k0s config passed to the k0s binary. + Config string `json:"config,omitempty"` + + DistroCommon `json:",inline"` + DistroContainer `json:",inline"` +} + +type DistroCommon struct { + // Env are extra environment variables to use for the main container. + Env []map[string]interface{} `json:"env,omitempty"` + // Resources are the resources for the distro init container + Resources map[string]interface{} `json:"resources,omitempty"` + // SecurityContext can be used for the distro init container + SecurityContext map[string]interface{} `json:"securityContext,omitempty"` +} + +type DistroContainer struct { + // Image is the distro image + Image Image `json:"image,omitempty"` + // ImagePullPolicy is the pull policy for the distro image + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // Command is the command to start the distro binary. This will override the existing command. + Command []string `json:"command,omitempty"` + // ExtraArgs are additional arguments to pass to the distro binary. + ExtraArgs []string `json:"extraArgs,omitempty"` +} + +type DistroContainerDisabled struct { + // Disabled signals this container should be disabled. + Disabled bool `json:"disabled,omitempty"` + // Image is the distro image + Image Image `json:"image,omitempty"` + // ImagePullPolicy is the pull policy for the distro image + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // Command is the command to start the distro binary. This will override the existing command. + Command []string `json:"command,omitempty"` + // ExtraArgs are additional arguments to pass to the distro binary. + ExtraArgs []string `json:"extraArgs,omitempty"` +} + +type Image struct { + // Repository is the registry and repository of the container image, e.g. my-registry.com/my-repo/my-image + Repository string `json:"repository,omitempty"` + // Tag is the tag of the container image, e.g. latest + Tag string `json:"tag,omitempty"` +} + +// LocalObjectReference contains enough information to let you locate the +// referenced object inside the same namespace. +type LocalObjectReference struct { + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name,omitempty"` +} + +type VirtualClusterKubeConfig struct { + // KubeConfig is the virtual cluster kube config path. + KubeConfig string `json:"kubeConfig,omitempty"` + // ServerCAKey is the server ca key path. + ServerCAKey string `json:"serverCAKey,omitempty"` + // ServerCAKey is the server ca cert path. + ServerCACert string `json:"serverCACert,omitempty"` + // ServerCAKey is the client ca cert path. + ClientCACert string `json:"clientCACert,omitempty"` + // RequestHeaderCACert is the request header ca cert path. + RequestHeaderCACert string `json:"requestHeaderCACert,omitempty"` +} + +type BackingStore struct { + // EmbeddedEtcd defines to use embedded etcd as a storage backend for the vCluster + EmbeddedEtcd EmbeddedEtcd `json:"embeddedEtcd,omitempty" product:"pro"` + // ExternalEtcd defines to use an external etcd deployed by the helm chart as a storage backend for the vCluster + ExternalEtcd ExternalEtcd `json:"externalEtcd,omitempty"` +} + +type EmbeddedEtcd struct { + // Enabled defines if the embedded etcd should be used. + Enabled bool `json:"enabled,omitempty"` + // MigrateFromExternalEtcd signals that vCluster should migrate from the external etcd. + MigrateFromExternalEtcd bool `json:"migrateFromExternalEtcd,omitempty"` +} + +type ExternalEtcd struct { + // Enabled defines if the external etcd should be used. + Enabled bool `json:"enabled,omitempty"` + + // StatefulSet holds options for the external etcd statefulSet. + StatefulSet ExternalEtcdStatefulSet `json:"statefulSet,omitempty"` + // Service holds options for the external etcd service. + Service ExternalEtcdService `json:"service,omitempty"` + // HeadlessService holds options for the external etcd headless service. + HeadlessService ExternalEtcdHeadlessService `json:"headlessService,omitempty"` +} + +type ExternalEtcdService struct { + // Enabled defines if the etcd service should be deployed + Enabled bool `json:"enabled,omitempty"` + // Annotations are extra annotations for the external etcd service + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ExternalEtcdHeadlessService struct { + // Enabled defines if the etcd headless service should be deployed + Enabled bool `json:"enabled,omitempty"` + // Annotations are extra annotations for the external etcd headless service + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ExternalEtcdStatefulSet struct { + // Enabled defines if the statefulSet should be deployed + Enabled bool `json:"enabled,omitempty"` + // Image is the image to use for the external etcd statefulSet + Image Image `json:"image,omitempty"` + // ImagePullPolicy is the pull policy for the external etcd image + ImagePullPolicy string `json:"imagePullPolicy,omitempty"` + // Env are extra environment variables + Env []map[string]interface{} `json:"env,omitempty"` + // ExtraArgs are appended to the etcd command. + ExtraArgs []string `json:"extraArgs,omitempty"` + + // Resources the etcd can consume + Resources Resources `json:"resources,omitempty"` + // Pods defines extra metadata for the etcd pods. + Pods LabelsAndAnnotations `json:"pods,omitempty"` + // HighAvailability are high availability options + HighAvailability ExternalEtcdHighAvailability `json:"highAvailability,omitempty"` + // Scheduling options for the etcd pods. + Scheduling ControlPlaneScheduling `json:"scheduling,omitempty"` + // Security options for the etcd pods. + Security ControlPlaneSecurity `json:"security,omitempty"` + // Persistence options for the etcd pods. + Persistence ControlPlanePersistence `json:"persistence,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type Resources struct { + // Limits are resource limits for the container + Limits map[string]interface{} `json:"limits,omitempty"` + // Requests are minimal resources that will be consumed by the container + Requests map[string]interface{} `json:"requests,omitempty"` +} + +type ExternalEtcdHighAvailability struct { + // Replicas are the amount of pods to use. + Replicas int `json:"replicas,omitempty"` +} + +type HostPathMapper struct { + // Enabled specifies if the host path mapper will be used + Enabled bool `json:"enabled,omitempty"` + // Central specifies if the central host path mapper will be used + Central bool `json:"central,omitempty" product:"pro"` +} + +type CoreDNS struct { + // Enabled defines if coredns is enabled + Enabled bool `json:"enabled,omitempty"` + // Embedded defines if vCluster will start the embedded coredns service + Embedded bool `json:"embedded,omitempty" product:"pro"` + // Service holds extra options for the coredns service deployed within the vCluster + Service CoreDNSService `json:"service,omitempty"` + // Deployment holds extra options for the coredns deployment deployed within the vCluster + Deployment CoreDNSDeployment `json:"deployment,omitempty"` + + // OverwriteConfig can be used to overwrite the coredns config + OverwriteConfig string `json:"overwriteConfig,omitempty"` + // OverwriteManifests can be used to overwrite the coredns manifests used to deploy coredns + OverwriteManifests string `json:"overwriteManifests,omitempty"` +} + +type CoreDNSService struct { + // Spec holds extra options for the coredns service + Spec map[string]interface{} `json:"spec,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type CoreDNSDeployment struct { + // Image is the coredns image to use + Image string `json:"image,omitempty"` + // Replicas is the amount of coredns pods to run. + Replicas int `json:"replicas,omitempty"` + // NodeSelector is the node selector to use for coredns. + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // Resources are the desired resources for coredns. + Resources Resources `json:"resources,omitempty"` + // Pods is additional metadata for the coredns pods. + Pods LabelsAndAnnotations `json:"pods,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type ControlPlaneProxy struct { + // BindAddress under which the vCluster will expose the proxy. + BindAddress string `json:"bindAddress,omitempty"` + // Port under which the vCluster will expose the proxy. + Port int `json:"port,omitempty"` + // ExtraSANs are extra hostnames to sign the vCluster proxy certificate for. + ExtraSANs []string `json:"extraSANs,omitempty"` +} + +type ControlPlaneService struct { + // Enabled defines if the control plane service should be enabled + Enabled bool `json:"enabled,omitempty"` + + // Spec allows you to configure extra service options. + Spec map[string]interface{} `json:"spec,omitempty"` + // KubeletNodePort is the node port where the fake kubelet is exposed. Defaults to 0. + KubeletNodePort int `json:"kubeletNodePort,omitempty"` + // HTTPSNodePort is the node port where https is exposed. Defaults to 0. + HTTPSNodePort int `json:"httpsNodePort,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type ControlPlaneIngress struct { + // Enabled defines if the control plane ingress should be enabled + Enabled bool `json:"enabled,omitempty"` + + // Host is the host where vCluster will be reachable + Host string `json:"host,omitempty"` + // PathType is the path type of the ingress + PathType string `json:"pathType,omitempty"` + // Spec allows you to configure extra ingress options. + Spec map[string]interface{} `json:"spec,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type ControlPlaneHighAvailability struct { + // Replicas is the amount of replicas to use for the statefulSet. + Replicas int32 `json:"replicas,omitempty"` + + // LeaseDuration is the time to lease for the leader. + LeaseDuration int `json:"leaseDuration,omitempty"` + + // RenewDeadline is the deadline to renew a lease for the leader. + RenewDeadline int `json:"renewDeadline,omitempty"` + + // RetryPeriod is the time until a replica will retry to get a lease. + RetryPeriod int `json:"retryPeriod,omitempty"` +} + +type ControlPlaneAdvanced struct { + // DefaultImageRegistry will be used as a prefix for all internal images deployed by vCluster or helm. This makes it easy to + // upload all required vCluster images to a single private repository and set this value. Workload images are not affected by this. + DefaultImageRegistry string `json:"defaultImageRegistry,omitempty"` + + // VirtualScheduler defines if a scheduler should be used within the vCluster or the scheduling decision for workloads will be made by the host cluster. + VirtualScheduler EnableSwitch `json:"virtualScheduler,omitempty"` + + // ServiceAccount specifies options for the vCluster control-plane service account. + ServiceAccount ControlPlaneServiceAccount `json:"serviceAccount,omitempty"` + + // WorkloadServiceAccount specifies options for the service account that will be used for the workloads that run within the vCluster. + WorkloadServiceAccount ControlPlaneWorkloadServiceAccount `json:"workloadServiceAccount,omitempty"` + + // HeadlessService specifies options for the headless service used for the vCluster statefulSet. + HeadlessService ControlPlaneHeadlessService `json:"headlessService,omitempty"` + + // GlobalMetadata is metadata that will be added to all resources deployed by helm. + GlobalMetadata ControlPlaneGlobalMetadata `json:"globalMetadata,omitempty"` +} + +type ControlPlaneHeadlessService struct { + // Annotations are extra annotations for this resource. + Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. + Labels map[string]string `json:"labels,omitempty"` +} + +type ControlPlanePersistence struct { + // VolumeClaim can be used to configure the persistent volume claim. + VolumeClaim VolumeClaim `json:"volumeClaim,omitempty"` + // VolumeClaimTemplates defines the volumeClaimTemplates for the statefulSet + VolumeClaimTemplates []map[string]interface{} `json:"volumeClaimTemplates,omitempty"` + // AddVolumes defines extra volumes for the pod + AddVolumes []map[string]interface{} `json:"addVolumes,omitempty"` + // AddVolumeMounts defines extra volume mounts for the container + AddVolumeMounts []VolumeMount `json:"addVolumeMounts,omitempty"` +} + +type VolumeClaim struct { + // Disabled signals to disable deploying a persistent volume claim. If false, vCluster will automatically determine + // based on the chosen distro and other options if this is required. + Disabled bool `json:"disabled,omitempty"` + // AccessModes are the persistent volume claim access modes. + AccessModes []string `json:"accessModes,omitempty"` + // RetentionPolicy is the persistent volume claim retention policy. + RetentionPolicy string `json:"retentionPolicy,omitempty"` + // Size is the persistent volume claim storage size. + Size string `json:"size,omitempty"` + // StorageClass is the persistent volume claim storage class. + StorageClass string `json:"storageClass,omitempty"` +} + +// VolumeMount describes a mounting of a Volume within a container. +type VolumeMount struct { + // This must match the Name of a Volume. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // Mounted read-only if true, read-write otherwise (false or unspecified). + // Defaults to false. + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,2,opt,name=readOnly"` + // Path within the container at which the volume should be mounted. Must + // not contain ':'. + MountPath string `json:"mountPath" protobuf:"bytes,3,opt,name=mountPath"` + // Path within the volume from which the container's volume should be mounted. + // Defaults to "" (volume's root). + SubPath string `json:"subPath,omitempty" protobuf:"bytes,4,opt,name=subPath"` + // mountPropagation determines how mounts are propagated from the host + // to container and the other way around. + // When not set, MountPropagationNone is used. + // This field is beta in 1.10. + MountPropagation *string `json:"mountPropagation,omitempty" protobuf:"bytes,5,opt,name=mountPropagation,casttype=MountPropagationMode"` + // Expanded path within the volume from which the container's volume should be mounted. + // Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + // Defaults to "" (volume's root). + // SubPathExpr and SubPath are mutually exclusive. + SubPathExpr string `json:"subPathExpr,omitempty" protobuf:"bytes,6,opt,name=subPathExpr"` +} + +type ControlPlaneScheduling struct { + // NodeSelector is the node selector to apply to the pod. + NodeSelector map[string]interface{} `json:"nodeSelector,omitempty"` + // Affinity is the affinity to apply to the pod. + Affinity map[string]interface{} `json:"affinity,omitempty"` + // Tolerations are the tolerations to apply to the pod. + Tolerations []interface{} `json:"tolerations,omitempty"` + // PriorityClassName is the priority class name for the the pod. + PriorityClassName string `json:"priorityClassName,omitempty"` + // PodManagementPolicy is the statefulSet pod management policy. + PodManagementPolicy string `json:"podManagementPolicy,omitempty"` + // TopologySpreadConstraints are the topology spread constraints for the pod. + TopologySpreadConstraints []interface{} `json:"topologySpreadConstraints,omitempty"` +} + +type ControlPlaneServiceAccount struct { + // Enabled specifies if the service account should get deployed. + Enabled bool `json:"enabled,omitempty"` + // Name specifies what name to use for the service account. + Name string `json:"name,omitempty"` + // ImagePullSecrets defines extra image pull secrets for the service account. + ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty"` + // Annotations are extra annotations for this resource. + Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. + Labels map[string]string `json:"labels,omitempty"` +} + +type ControlPlaneWorkloadServiceAccount struct { + // Enabled specifies if the service account for the workloads should get deployed. + Enabled bool `json:"enabled,omitempty"` + // Name specifies what name to use for the service account for the vCluster workloads. + Name string `json:"name,omitempty"` + // ImagePullSecrets defines extra image pull secrets for the workload service account. + ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty"` + // Annotations are extra annotations for this resource. + Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. + Labels map[string]string `json:"labels,omitempty"` +} + +type ControlPlaneProbes struct { + // LivenessProbe specifies if the liveness probe for the container should be enabled + LivenessProbe EnableSwitch `json:"livenessProbe,omitempty"` + // ReadinessProbe specifies if the readiness probe for the container should be enabled + ReadinessProbe EnableSwitch `json:"readinessProbe,omitempty"` + // StartupProbe specifies if the startup probe for the container should be enabled + StartupProbe EnableSwitch `json:"startupProbe,omitempty"` +} + +type ControlPlaneSecurity struct { + // PodSecurityContext specifies security context options on the pod level. + PodSecurityContext map[string]interface{} `json:"podSecurityContext,omitempty"` + // ContainerSecurityContext specifies security context options on the container level. + ContainerSecurityContext map[string]interface{} `json:"containerSecurityContext,omitempty"` +} + +type ControlPlaneGlobalMetadata struct { + // Annotations are extra annotations for this resource. + Annotations map[string]string `json:"annotations,omitempty"` +} + +type LabelsAndAnnotations struct { + // Annotations are extra annotations for this resource. + Annotations map[string]string `json:"annotations,omitempty"` + // Labels are extra labels for this resource. + Labels map[string]string `json:"labels,omitempty"` +} + +type Policies struct { + // NetworkPolicy specifies network policy options. + NetworkPolicy NetworkPolicy `json:"networkPolicy,omitempty"` + // PodSecurityStandard that can be enforced can be one of: empty (""), baseline, restricted or privileged + PodSecurityStandard string `json:"podSecurityStandard,omitempty"` + // ResourceQuota specifies resource quota options. + ResourceQuota ResourceQuota `json:"resourceQuota,omitempty"` + // LimitRange specifies limit range options. + LimitRange LimitRange `json:"limitRange,omitempty"` + // CentralAdmission defines what validating or mutating webhooks should be enforced within the vCluster. + CentralAdmission CentralAdmission `json:"centralAdmission,omitempty" product:"pro"` +} + +type ResourceQuota struct { + // Enabled defines if the resource quota should be enabled. + Enabled bool `json:"enabled,omitempty"` + // Quota are the quota options + Quota map[string]interface{} `json:"quota,omitempty"` + // ScopeSelector is the resource quota scope selector + ScopeSelector ScopeSelector `json:"scopeSelector,omitempty"` + // Scopes are the resource quota scopes + Scopes []string `json:"scopes,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type ScopeSelector struct { + MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty"` +} + +type LabelSelectorRequirement struct { + // key is the label key that the selector applies to. + Key string `json:"key"` + // operator represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists and DoesNotExist. + Operator string `json:"operator"` + // values is an array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. This array is replaced during a strategic + // merge patch. + Values []string `json:"values,omitempty"` +} + +type LimitRange struct { + // Enabled defines if the limit range should be deployed by vCluster. + Enabled bool `json:"enabled,omitempty"` + + // Default are the default limits for the limit range + Default map[string]interface{} `json:"default,omitempty"` + // DefaultRequest are the default request options for the limit range + DefaultRequest map[string]interface{} `json:"defaultRequest,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type NetworkPolicy struct { + // Enabled defines if the network policy should be deployed by vCluster. + Enabled bool `json:"enabled,omitempty"` + + FallbackDNS string `json:"fallbackDns,omitempty"` + OutgoingConnections OutgoingConnections `json:"outgoingConnections,omitempty"` + + LabelsAndAnnotations `json:",inline"` +} + +type OutgoingConnections struct { + IPBlock IPBlock `json:"ipBlock,omitempty"` +} + +// IPBlock describes a particular CIDR (Ex. "192.168.1.0/24","2001:db8::/64") that is allowed +// to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs +// that should not be included within this rule. +type IPBlock struct { + // cidr is a string representing the IPBlock + // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + CIDR string `json:"cidr,omitempty"` + + // except is a slice of CIDRs that should not be included within an IPBlock + // Valid examples are "192.168.1.0/24" or "2001:db8::/64" + // Except values will be rejected if they are outside the cidr range + // +optional + Except []string `json:"except,omitempty"` +} + +type CentralAdmission struct { + // ValidatingWebhooks are validating webhooks that should be enforced in the vCluster + ValidatingWebhooks []interface{} `json:"validatingWebhooks,omitempty"` + // MutatingWebhooks are mutating webhooks that should be enforced in the vCluster + MutatingWebhooks []interface{} `json:"mutatingWebhooks,omitempty"` +} + +type RBAC struct { + // Role holds vCluster role configuration + Role RBACRole `json:"role,omitempty"` + // ClusterRole holds vCluster cluster role configuration + ClusterRole RBACClusterRole `json:"clusterRole,omitempty"` +} + +type RBACClusterRole struct { + // Disabled defines if the cluster role should be disabled. Otherwise, its automatically determined if vCluster requires a cluster role. + Disabled bool `json:"disabled,omitempty"` + // ExtraRules will add rules to the cluster role. + ExtraRules []map[string]interface{} `json:"extraRules,omitempty"` + // OverwriteRules will overwrite the cluster role rules completely. + OverwriteRules []map[string]interface{} `json:"overwriteRules,omitempty"` +} + +type RBACRole struct { + // Enabled + Enabled bool `json:"enabled,omitempty"` + // ExtraRules will add rules to the role. + ExtraRules []map[string]interface{} `json:"extraRules,omitempty"` + // OverwriteRules will overwrite the role rules completely. + OverwriteRules []map[string]interface{} `json:"overwriteRules,omitempty"` +} + +type Telemetry struct { + // Disabled specifies that the telemetry for vCluster control plane should be disabled. + Disabled bool `json:"disabled,omitempty"` + InstanceCreator string `json:"instanceCreator,omitempty"` + MachineID string `json:"machineID,omitempty"` + PlatformUserID string `json:"platformUserID,omitempty"` + PlatformInstanceID string `json:"platformInstanceID,omitempty"` +} + +type Experimental struct { + // Deploy allows you to configure manifests and helm charts to deploy within the vCluster. + Deploy ExperimentalDeploy `json:"deploy,omitempty"` + + // SyncSettings are advanced settings for the syncer controller. + SyncSettings ExperimentalSyncSettings `json:"syncSettings,omitempty"` + + // GenericSync holds options to generically sync resources from vCluster to host. + GenericSync ExperimentalGenericSync `json:"genericSync,omitempty"` + + // MultiNamespaceMode tells vCluster to sync to multiple namespaces instead of a single one. This will map each vCluster namespace to a single namespace in the host cluster. + MultiNamespaceMode ExperimentalMultiNamespaceMode `json:"multiNamespaceMode,omitempty"` + + // IsolatedControlPlane is a feature to run the vCluster control plane in a different Kubernetes cluster than the workloads themselves. + IsolatedControlPlane ExperimentalIsolatedControlPlane `json:"isolatedControlPlane,omitempty"` + + // VirtualClusterKubeConfig allows you to override distro specifics and specify where vCluster will find the required certificates and vCluster config. + VirtualClusterKubeConfig VirtualClusterKubeConfig `json:"virtualClusterKubeConfig,omitempty"` + + // DenyProxyRequests denies certain requests in the vCluster proxy. + DenyProxyRequests []DenyRule `json:"denyProxyRequests,omitempty" pro:"true"` +} + +type ExperimentalMultiNamespaceMode struct { + // Enabled specifies if multi namespace mode should get enabled + Enabled bool `json:"enabled,omitempty"` + + // NamespaceLabels are extra labels that will be added by vCluster to each created namespace. + NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"` +} + +type ExperimentalIsolatedControlPlane struct { + // Enabled specifies if the isolated control plane feature should be enabled. + Enabled bool `json:"enabled,omitempty"` + // Headless states that helm should deploy the vCluster in headless mode for the isolated control plane. + Headless bool `json:"headless,omitempty"` + // KubeConfig is the path where to find the remote workload cluster kube config. + KubeConfig string `json:"kubeConfig,omitempty"` + // Namespace is the namespace where to sync the workloads into. + Namespace string `json:"namespace,omitempty"` + // Service is the vCluster service in the remote cluster. + Service string `json:"service,omitempty"` +} + +type ExperimentalSyncSettings struct { + // DisableSync will not sync any resources and disable most control plane functionality. + DisableSync bool `json:"disableSync,omitempty"` + // RewriteKubernetesService will rewrite the kubernetes service to point to the vCluster if disableSync is enabled + RewriteKubernetesService bool `json:"rewriteKubernetesService,omitempty"` + + // TargetNamespace is the namespace where the workloads should get synced to. + TargetNamespace string `json:"targetNamespace,omitempty"` + // SetOwner specifies if vCluster should set an owner reference on the synced objects to the vCluster service. This allows for easy garbage collection. + SetOwner bool `json:"setOwner,omitempty"` + // SyncLabels are labels that should get not rewritten when syncing from vCluster. + SyncLabels []string `json:"syncLabels,omitempty"` +} + +type ExperimentalDeploy struct { + // Manifests are raw kubernetes manifests that should get applied within the vCluster. + Manifests string `json:"manifests,omitempty"` + // ManifestsTemplate is a kubernetes manifest template that will be rendered with vCluster values before applying it within the vCluster. + ManifestsTemplate string `json:"manifestsTemplate,omitempty"` + // Helm are helm charts that should get deployed into the vCluster + Helm []ExperimentalDeployHelm `json:"helm,omitempty"` +} + +type ExperimentalDeployHelm struct { + // Chart defines what chart should get deployed. + Chart ExperimentalDeployHelmChart `json:"chart,omitempty"` + // Release defines what release should get deployed. + Release ExperimentalDeployHelmRelease `json:"release,omitempty"` + // Values defines what values should get used. + Values string `json:"values,omitempty"` + // Timeout defines the timeout for helm + Timeout string `json:"timeout,omitempty"` + // Bundle allows to compress the helm chart and specify this instead of an online chart + Bundle string `json:"bundle,omitempty"` +} + +type ExperimentalDeployHelmRelease struct { + // Name of the release + Name string `json:"name,omitempty"` + // Namespace of the release + Namespace string `json:"namespace,omitempty"` +} + +type ExperimentalDeployHelmChart struct { + Name string `json:"name,omitempty"` + Repo string `json:"repo,omitempty"` + Insecure bool `json:"insecure,omitempty"` + Version string `json:"version,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +type Platform struct { + // APIKey defines how vCluster can find the api key used for the platform. + APIKey PlatformAPIKey `json:"apiKey,omitempty"` + + // Name is the name of the vCluster instance in the vCluster platform + Name string `json:"name,omitempty"` + + // Owner is the desired owner of the vCluster within the vCluster platform. If empty will take the current user. + Owner PlatformOwner `json:"owner,omitempty"` + + // Project is the project within the platform where the vCluster should connect to. + Project string `json:"project,omitempty"` +} + +type PlatformOwner struct { + // User is the user id within the platform. This is mutually exclusive with team. + User string `json:"user,omitempty"` + + // Team is the team id within the platform. This is mutually exclusive with user. + Team string `json:"team,omitempty"` +} + +type PlatformAPIKey struct { + // Value specifies the api key as a regular text value. + Value string `json:"value,omitempty"` + + // SecretRef defines where to find the platform api key. By default vCluster will search in the following locations in this precedence: + // * platform.apiKey.value + // * environment variable called LICENSE + // * secret specified under platform.secret.name + // * secret called "vcluster-platform-api-key" in the vCluster namespace + SecretRef PlatformAPIKeySecretReference `json:"secretRef,omitempty"` +} + +// PlatformAPIKeySecretReference defines where to find the platform api key. The secret key name doesn't matter as long as the secret only contains a single key. +type PlatformAPIKeySecretReference struct { + // Name is the name of the secret where the platform api key is stored. This defaults to vcluster-platform-api-key if undefined. + Name string `json:"name,omitempty"` + + // Namespace defines the namespace where the api key secret should be retrieved from. If this is not equal to the namespace + // where the vCluster is deployed, you need to make sure vCluster has access to this other namespace. + Namespace string `json:"namespace,omitempty"` +} + +type ExperimentalGenericSync struct { + // Version is the config version + Version string `json:"version,omitempty" yaml:"version,omitempty"` + + // Exports syncs a resource from the virtual cluster to the host + Exports []*Export `json:"export,omitempty" yaml:"export,omitempty"` + + // Imports syncs a resource from the host cluster to virtual cluster + Imports []*Import `json:"import,omitempty" yaml:"import,omitempty"` + + // Hooks are hooks that can be used to inject custom patches before syncing + Hooks *Hooks `json:"hooks,omitempty" yaml:"hooks,omitempty"` + + ClusterRole ExperimentalGenericSyncExtraRules `json:"clusterRole,omitempty"` + Role ExperimentalGenericSyncExtraRules `json:"role,omitempty"` +} + +type ExperimentalGenericSyncExtraRules struct { + ExtraRules []interface{} `json:"extraRules,omitempty"` +} + +type Hooks struct { + // HostToVirtual is a hook that is executed before syncing from the host to the virtual cluster + HostToVirtual []*Hook `json:"hostToVirtual,omitempty" yaml:"hostToVirtual,omitempty"` + + // VirtualToHost is a hook that is executed before syncing from the virtual to the host cluster + VirtualToHost []*Hook `json:"virtualToHost,omitempty" yaml:"virtualToHost,omitempty"` +} + +type Hook struct { + TypeInformation + + // Verbs are the verbs that the hook should mutate + Verbs []string `json:"verbs,omitempty" yaml:"verbs,omitempty"` + + // Patches are the patches to apply on the object to be synced + Patches []*Patch `json:"patches,omitempty" yaml:"patches,omitempty"` +} + +type Import struct { + SyncBase `json:",inline" yaml:",inline"` +} + +type SyncBase struct { + TypeInformation `json:",inline" yaml:",inline"` + + Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"` + + // ReplaceWhenInvalid determines if the controller should try to recreate the object + // if there is a problem applying + ReplaceWhenInvalid bool `json:"replaceOnConflict,omitempty" yaml:"replaceOnConflict,omitempty"` + + // Patches are the patches to apply on the virtual cluster objects + // when syncing them from the host cluster + Patches []*Patch `json:"patches,omitempty" yaml:"patches,omitempty"` + + // ReversePatches are the patches to apply to host cluster objects + // after it has been synced to the virtual cluster + ReversePatches []*Patch `json:"reversePatches,omitempty" yaml:"reversePatches,omitempty"` +} + +type Export struct { + SyncBase `json:",inline" yaml:",inline"` + + // Selector is a label selector to select the synced objects in the virtual cluster. + // If empty, all objects will be synced. + Selector *Selector `json:"selector,omitempty" yaml:"selector,omitempty"` +} + +type TypeInformation struct { + // APIVersion of the object to sync + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + + // Kind of the object to sync + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` +} + +type Selector struct { + // LabelSelector are the labels to select the object from + LabelSelector map[string]string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"` +} + +type Patch struct { + // Operation is the type of the patch + Operation PatchType `json:"op,omitempty" yaml:"op,omitempty"` + + // FromPath is the path from the other object + FromPath string `json:"fromPath,omitempty" yaml:"fromPath,omitempty"` + + // Path is the path of the patch + Path string `json:"path,omitempty" yaml:"path,omitempty"` + + // NamePath is the path to the name of a child resource within Path + NamePath string `json:"namePath,omitempty" yaml:"namePath,omitempty"` + + // NamespacePath is path to the namespace of a child resource within Path + NamespacePath string `json:"namespacePath,omitempty" yaml:"namespacePath,omitempty"` + + // Value is the new value to be set to the path + Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` + + // Regex - is regular expresion used to identify the Name, + // and optionally Namespace, parts of the field value that + // will be replaced with the rewritten Name and/or Namespace + Regex string `json:"regex,omitempty" yaml:"regex,omitempty"` + ParsedRegex *regexp.Regexp `json:"-" yaml:"-"` + + // Conditions are conditions that must be true for + // the patch to get executed + Conditions []*PatchCondition `json:"conditions,omitempty" yaml:"conditions,omitempty"` + + // Ignore determines if the path should be ignored if handled as a reverse patch + Ignore *bool `json:"ignore,omitempty" yaml:"ignore,omitempty"` + + // Sync defines if a specialized syncer should be initialized using values + // from the rewriteName operation as Secret/Configmap names to be synced + Sync *PatchSync `json:"sync,omitempty" yaml:"sync,omitempty"` +} + +type PatchType string + +const ( + PatchTypeRewriteName PatchType = "rewriteName" + PatchTypeRewriteLabelKey PatchType = "rewriteLabelKey" + PatchTypeRewriteLabelSelector PatchType = "rewriteLabelSelector" + PatchTypeRewriteLabelExpressionsSelector PatchType = "rewriteLabelExpressionsSelector" + + PatchTypeCopyFromObject PatchType = "copyFromObject" + PatchTypeAdd PatchType = "add" + PatchTypeReplace PatchType = "replace" + PatchTypeRemove PatchType = "remove" +) + +type PatchCondition struct { + // Path is the path within the object to select + Path string `json:"path,omitempty" yaml:"path,omitempty"` + + // SubPath is the path below the selected object to select + SubPath string `json:"subPath,omitempty" yaml:"subPath,omitempty"` + + // Equal is the value the path should be equal to + Equal interface{} `json:"equal,omitempty" yaml:"equal,omitempty"` + + // NotEqual is the value the path should not be equal to + NotEqual interface{} `json:"notEqual,omitempty" yaml:"notEqual,omitempty"` + + // Empty means that the path value should be empty or unset + Empty *bool `json:"empty,omitempty" yaml:"empty,omitempty"` +} + +type PatchSync struct { + Secret *bool `json:"secret,omitempty" yaml:"secret,omitempty"` + ConfigMap *bool `json:"configmap,omitempty" yaml:"configmap,omitempty"` +} + +type DenyRule struct { + // The name of the check. + // +optional + Name string `json:"name,omitempty"` + + // Namespace describe a list of namespaces that will be affected by the check. + // An empty list means that all namespaces will be affected. + // In case of ClusterScoped rules, only the Namespace resource is affected. + // +optional + Namespaces []string `json:"namespaces,omitempty"` + + // Rules describes on which verbs and on what resources/subresources the webhook is enforced. + // The webhook is enforced if it matches any Rule. + // The version of the request must match the rule version exactly. Equivalent matching is not supported. + // +optional + Rules []RuleWithVerbs `json:"rules,omitempty"` + + // ExcludedUsers describe a list of users for which the checks will be skipped. + // Impersonation attempts on these users will still be subjected to the checks. + // +optional + ExcludedUsers []string `json:"excludedUsers,omitempty"` +} + +type RuleWithVerbs struct { + // APIGroups is the API groups the resources belong to. '*' is all groups. + // If '*' is present, the length of the slice must be one. + // Required. + // +listType=atomic + APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"` + + // APIVersions is the API versions the resources belong to. '*' is all versions. + // If '*' is present, the length of the slice must be one. + // Required. + // +listType=atomic + APIVersions []string `json:"apiVersions,omitempty" protobuf:"bytes,2,rep,name=apiVersions"` + + // Resources is a list of resources this rule applies to. + // + // For example: + // 'pods' means pods. + // 'pods/log' means the log subresource of pods. + // '*' means all resources, but not subresources. + // 'pods/*' means all subresources of pods. + // '*/scale' means all scale subresources. + // '*/*' means all resources and their subresources. + // + // If wildcard is present, the validation rule will ensure resources do not + // overlap with each other. + // + // Depending on the enclosing object, subresources might not be allowed. + // Required. + // +listType=atomic + Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` + + // scope specifies the scope of this rule. + // Valid values are "Cluster", "Namespaced", and "*" + // "Cluster" means that only cluster-scoped resources will match this rule. + // Namespace API objects are cluster-scoped. + // "Namespaced" means that only namespaced resources will match this rule. + // "*" means that there are no scope restrictions. + // Subresources match the scope of their parent resource. + // Default is "*". + // + // +optional + Scope *string `json:"scope,omitempty" protobuf:"bytes,4,rep,name=scope"` + + // Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch. + // For non-resource requests, this is the lowercase http verb. + // If '*' is present, the length of the slice must be one. + // Required. + // +listType=atomic + Verbs []string `json:"operations,omitempty"` +} diff --git a/config/default_extra_values.go b/config/default_extra_values.go new file mode 100644 index 000000000..0d4a70f0a --- /dev/null +++ b/config/default_extra_values.go @@ -0,0 +1,414 @@ +package config + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + "strings" +) + +const ( + K3SDistro = "k3s" + K8SDistro = "k8s" + K0SDistro = "k0s" + EKSDistro = "eks" + Unknown = "unknown" +) + +// K3SVersionMap holds the supported k3s versions +var K3SVersionMap = map[string]string{ + "1.29": "rancher/k3s:v1.29.0-k3s1", + "1.28": "rancher/k3s:v1.28.5-k3s1", + "1.27": "rancher/k3s:v1.27.9-k3s1", + "1.26": "rancher/k3s:v1.26.12-k3s1", +} + +// K0SVersionMap holds the supported k0s versions +var K0SVersionMap = map[string]string{ + "1.29": "k0sproject/k0s:v1.29.1-k0s.0", + "1.28": "k0sproject/k0s:v1.28.2-k0s.0", + "1.27": "k0sproject/k0s:v1.27.6-k0s.0", + "1.26": "k0sproject/k0s:v1.26.9-k0s.0", +} + +// K8SAPIVersionMap holds the supported k8s api servers +var K8SAPIVersionMap = map[string]string{ + "1.29": "registry.k8s.io/kube-apiserver:v1.29.0", + "1.28": "registry.k8s.io/kube-apiserver:v1.28.4", + "1.27": "registry.k8s.io/kube-apiserver:v1.27.8", + "1.26": "registry.k8s.io/kube-apiserver:v1.26.11", +} + +// K8SControllerVersionMap holds the supported k8s controller managers +var K8SControllerVersionMap = map[string]string{ + "1.29": "registry.k8s.io/kube-controller-manager:v1.29.0", + "1.28": "registry.k8s.io/kube-controller-manager:v1.28.4", + "1.27": "registry.k8s.io/kube-controller-manager:v1.27.8", + "1.26": "registry.k8s.io/kube-controller-manager:v1.26.11", +} + +// K8SSchedulerVersionMap holds the supported k8s schedulers +var K8SSchedulerVersionMap = map[string]string{ + "1.29": "registry.k8s.io/kube-scheduler:v1.29.0", + "1.28": "registry.k8s.io/kube-scheduler:v1.28.4", + "1.27": "registry.k8s.io/kube-scheduler:v1.27.8", + "1.26": "registry.k8s.io/kube-scheduler:v1.26.11", +} + +// K8SEtcdVersionMap holds the supported etcd +var K8SEtcdVersionMap = map[string]string{ + "1.29": "registry.k8s.io/etcd:3.5.10-0", + "1.28": "registry.k8s.io/etcd:3.5.9-0", + "1.27": "registry.k8s.io/etcd:3.5.7-0", + "1.26": "registry.k8s.io/etcd:3.5.6-0", +} + +// EKSAPIVersionMap holds the supported eks api servers +var EKSAPIVersionMap = map[string]string{ + "1.28": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.28.2-eks-1-28-6", + "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.27.6-eks-1-27-13", + "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.26.9-eks-1-26-19", + "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.25.14-eks-1-25-23", +} + +// EKSControllerVersionMap holds the supported eks controller managers +var EKSControllerVersionMap = map[string]string{ + "1.28": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.28.2-eks-1-28-6", + "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.27.6-eks-1-27-13", + "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.26.9-eks-1-26-19", + "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.25.14-eks-1-25-23", +} + +// EKSSchedulerVersionMap holds the supported eks controller managers +var EKSSchedulerVersionMap = map[string]string{ + "1.28": "public.ecr.aws/eks-distro/kubernetes/kube-scheduler:v1.28.2-eks-1-28-6", + "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-scheduler:v1.27.6-eks-1-27-13", + "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-scheduler:v1.26.9-eks-1-26-19", + "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-scheduler:v1.25.14-eks-1-25-23", +} + +// EKSEtcdVersionMap holds the supported eks etcd +var EKSEtcdVersionMap = map[string]string{ + "1.28": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.9-eks-1-28-6", + "1.27": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-27-13", + "1.26": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-26-19", + "1.25": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-25-23", +} + +// EKSCoreDNSVersionMap holds the supported eks core dns +var EKSCoreDNSVersionMap = map[string]string{ + "1.28": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-28-6", + "1.27": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-27-13", + "1.26": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-26-19", + "1.25": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-25-23", +} + +// ExtraValuesOptions holds the chart options +type ExtraValuesOptions struct { + Distro string + + Expose bool + NodePort bool + SyncNodes bool + KubernetesVersion KubernetesVersion + + DisableTelemetry bool + InstanceCreatorType string + MachineID string + PlatformInstanceID string + PlatformUserID string +} + +type Logger interface { + Info(msg string, keysAndValues ...any) +} + +type KubernetesVersion struct { + Major string + Minor string +} + +func GetExtraValues(options *ExtraValuesOptions, log Logger) (string, error) { + vConfig, err := getExtraValues(options, log) + if err != nil { + return "", fmt.Errorf("get extra values: %w", err) + } + + rawConfig, err := json.Marshal(vConfig) + if err != nil { + return "", fmt.Errorf("marshal extra values: %w", err) + } + + return string(rawConfig), nil +} + +func getExtraValues(options *ExtraValuesOptions, log Logger) (*Config, error) { + switch options.Distro { + case K3SDistro: + return getK3SExtraValues(options, log) + case K0SDistro: + return getK0SExtraValues(options, log) + case K8SDistro: + return getK8SExtraValues(options, log) + case EKSDistro: + return getEKSExtraValues(options, log) + } + + return &Config{}, nil +} + +var replaceRegEx = regexp.MustCompile("[^0-9]+") + +func getK3SExtraValues(options *ExtraValuesOptions, log Logger) (*Config, error) { + // get k3s image + image, err := getImageByVersion(options.KubernetesVersion, K3SVersionMap, log) + if err != nil { + return nil, err + } + + // build values + vConfig := &Config{} + if image != "" { + vConfig.ControlPlane.Distro.K3S.Image = parseImage(image) + } + + // add common release values + addCommonReleaseValues(vConfig, options) + return vConfig, nil +} + +func getK0SExtraValues(options *ExtraValuesOptions, log Logger) (*Config, error) { + // get k0s image + image, err := getImageByVersion(options.KubernetesVersion, K0SVersionMap, log) + if err != nil { + return nil, err + } + + // build values + vConfig := &Config{} + vConfig.ControlPlane.Distro.K0S.Enabled = true + if image != "" { + vConfig.ControlPlane.Distro.K0S.Image = parseImage(image) + } + + // add common release values + addCommonReleaseValues(vConfig, options) + return vConfig, nil +} + +func getEKSExtraValues(options *ExtraValuesOptions, log Logger) (*Config, error) { + // get api server image + apiImage, err := getImageByVersion(options.KubernetesVersion, EKSAPIVersionMap, log) + if err != nil { + return nil, err + } + + // get controller image + controllerImage, err := getImageByVersion(options.KubernetesVersion, EKSControllerVersionMap, log) + if err != nil { + return nil, err + } + + // get scheduler image + schedulerImage, err := getImageByVersion(options.KubernetesVersion, EKSSchedulerVersionMap, log) + if err != nil { + return nil, err + } + + // get etcd image + etcdImage, err := getImageByVersion(options.KubernetesVersion, EKSEtcdVersionMap, log) + if err != nil { + return nil, err + } + + // get coredns image + coreDNSImage, err := getImageByVersion(options.KubernetesVersion, EKSCoreDNSVersionMap, log) + if err != nil { + return nil, err + } + + // build values + vConfig := &Config{} + vConfig.ControlPlane.Distro.EKS.Enabled = true + if apiImage != "" { + vConfig.ControlPlane.Distro.EKS.APIServer.Image = parseImage(apiImage) + } + if controllerImage != "" { + vConfig.ControlPlane.Distro.EKS.ControllerManager.Image = parseImage(controllerImage) + } + if schedulerImage != "" { + vConfig.ControlPlane.Distro.EKS.Scheduler.Image = parseImage(schedulerImage) + } + if etcdImage != "" { + vConfig.ControlPlane.BackingStore.ExternalEtcd.StatefulSet.Image = parseImage(etcdImage) + } + if coreDNSImage != "" { + vConfig.ControlPlane.CoreDNS.Deployment.Image = coreDNSImage + } + + addCommonReleaseValues(vConfig, options) + return vConfig, nil +} + +func getK8SExtraValues(options *ExtraValuesOptions, log Logger) (*Config, error) { + // get api server image + apiImage, err := getImageByVersion(options.KubernetesVersion, K8SAPIVersionMap, log) + if err != nil { + return nil, err + } + + // get controller image + controllerImage, err := getImageByVersion(options.KubernetesVersion, K8SControllerVersionMap, log) + if err != nil { + return nil, err + } + + // get scheduler image + schedulerImage, err := getImageByVersion(options.KubernetesVersion, K8SSchedulerVersionMap, log) + if err != nil { + return nil, err + } + + // get etcd image + etcdImage, err := getImageByVersion(options.KubernetesVersion, K8SEtcdVersionMap, log) + if err != nil { + return nil, err + } + + // build values + vConfig := &Config{} + vConfig.ControlPlane.Distro.K8S.Enabled = true + if apiImage != "" { + vConfig.ControlPlane.Distro.K8S.APIServer.Image = parseImage(apiImage) + } + if controllerImage != "" { + vConfig.ControlPlane.Distro.K8S.ControllerManager.Image = parseImage(controllerImage) + } + if schedulerImage != "" { + vConfig.ControlPlane.Distro.K8S.Scheduler.Image = parseImage(schedulerImage) + } + if etcdImage != "" { + vConfig.ControlPlane.BackingStore.ExternalEtcd.StatefulSet.Image = parseImage(etcdImage) + } + + addCommonReleaseValues(vConfig, options) + return vConfig, nil +} + +func parseImage(image string) Image { + splitTag := strings.SplitN(image, ":", 2) + if len(splitTag) == 2 { + return Image{ + Repository: splitTag[0], + Tag: splitTag[1], + } + } + + return Image{} +} + +func getImageByVersion(kubernetesVersion KubernetesVersion, versionImageMap map[string]string, log Logger) (string, error) { + // check if there is a minor and major version + if kubernetesVersion.Minor == "" || kubernetesVersion.Major == "" { + return "", nil + } + + // find highest and lowest supported version for this map + highestMinorVersion := 0 + lowestMinorVersion := 0 + for version := range versionImageMap { + kubeVersion, err := ParseKubernetesVersionInfo(version) + if err != nil { + return "", fmt.Errorf("parse kube version %s: %w", version, err) + } + + minorVersion, err := strconv.Atoi(kubeVersion.Minor) + if err != nil { + return "", fmt.Errorf("convert minor version %s: %w", kubeVersion.Minor, err) + } + + if lowestMinorVersion == 0 || minorVersion < lowestMinorVersion { + lowestMinorVersion = minorVersion + } + if highestMinorVersion == 0 || minorVersion > highestMinorVersion { + highestMinorVersion = minorVersion + } + } + + // figure out what image to use + serverVersionString := getKubernetesVersion(kubernetesVersion) + serverMinorInt, err := getKubernetesMinorVersion(kubernetesVersion) + if err != nil { + return "", err + } + + // try to get from map + image, ok := versionImageMap[serverVersionString] + if !ok { + if serverMinorInt > highestMinorVersion { + log.Info(fmt.Sprintf("officially unsupported host server version %s, will fallback to virtual cluster version v1.%d", serverVersionString, highestMinorVersion)) + image = versionImageMap["1."+strconv.Itoa(highestMinorVersion)] + } else { + log.Info(fmt.Sprintf("officially unsupported host server version %s, will fallback to virtual cluster version v1.%d", serverVersionString, lowestMinorVersion)) + image = versionImageMap["1."+strconv.Itoa(lowestMinorVersion)] + } + } + + return image, nil +} + +func addCommonReleaseValues(config *Config, options *ExtraValuesOptions) { + if options.Expose { + if config.ControlPlane.Service.Spec == nil { + config.ControlPlane.Service.Spec = map[string]interface{}{} + } + + config.ControlPlane.Service.Spec["type"] = "LoadBalancer" + } else if options.NodePort { + if config.ControlPlane.Service.Spec == nil { + config.ControlPlane.Service.Spec = map[string]interface{}{} + } + + config.ControlPlane.Service.Spec["type"] = "NodePort" + } + + if options.SyncNodes { + config.Sync.FromHost.Nodes.Enabled = true + } + + if options.DisableTelemetry { + config.Telemetry.Disabled = true + } else if options.InstanceCreatorType != "" { + config.Telemetry.InstanceCreator = options.InstanceCreatorType + config.Telemetry.PlatformUserID = options.PlatformUserID + config.Telemetry.PlatformInstanceID = options.PlatformInstanceID + config.Telemetry.MachineID = options.MachineID + } +} + +func getKubernetesVersion(serverVersion KubernetesVersion) string { + return replaceRegEx.ReplaceAllString(serverVersion.Major, "") + "." + replaceRegEx.ReplaceAllString(serverVersion.Minor, "") +} + +func getKubernetesMinorVersion(serverVersion KubernetesVersion) (int, error) { + return strconv.Atoi(replaceRegEx.ReplaceAllString(serverVersion.Minor, "")) +} + +func ParseKubernetesVersionInfo(versionStr string) (*KubernetesVersion, error) { + if versionStr[0] == 'v' { + versionStr = versionStr[1:] + } + + splittedVersion := strings.Split(versionStr, ".") + if len(splittedVersion) != 2 && len(splittedVersion) != 3 { + return nil, fmt.Errorf("unrecognized kubernetes version %s, please use format vX.X", versionStr) + } + + major := splittedVersion[0] + minor := splittedVersion[1] + return &KubernetesVersion{ + Major: major, + Minor: minor, + }, nil +} diff --git a/devspace.yaml b/devspace.yaml index 8f7d7a3f3..be698a53a 100644 --- a/devspace.yaml +++ b/devspace.yaml @@ -24,65 +24,82 @@ deployments: helm: releaseName: vcluster chart: - name: ./charts/k8s - values: &values - autoDeletePersistentVolumeClaims: false - k3sToken: "devspace" - job: - enabled: false - fallbackHostDns: true - service: - type: NodePort - serviceCIDR: $([ $1 == "dev" ] && vcluster get service-cidr || echo "null") - tolerations: - - operator: "Exists" - serviceAccount: - create: false - name: default - rbac: - clusterRole: - create: true - role: - extended: true - syncer: - image: ${SYNCER_IMAGE} - multiNamespaceMode: - enabled: false - telemetry: - disabled: false - instanceCreatorType: "devspace" - machineID: "devspace" - proxy: - metricsServer: - nodes: - enabled: true - pods: + name: ./chart + values: + controlPlane: + distro: + k8s: enabled: true - sync: - generic: - clusterRole: - extraRules: - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["get", "list", "watch"] - # Config for testing - - apiGroups: ["cert-manager.io"] - resources: ["issuers", "certificates", "certificaterequests"] - verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] + statefulSet: + image: + repository: image(${SYNCER_IMAGE}) + tag: tag(${SYNCER_IMAGE}) + scheduling: + tolerations: + - operator: "Exists" + service: + spec: + type: NodePort + advanced: + serviceAccount: + enabled: false + name: default + telemetry: + disabled: true vcluster-k3s: helm: releaseName: vcluster chart: - name: ./charts/k3s - values: *values # reuses the same values defined under the &values anchor + name: ./chart + values: + controlPlane: + distro: + k3s: + enabled: true + statefulSet: + image: + repository: image(${SYNCER_IMAGE}) + tag: tag(${SYNCER_IMAGE}) + scheduling: + tolerations: + - operator: "Exists" + service: + spec: + type: NodePort + advanced: + serviceAccount: + enabled: false + name: default + telemetry: + disabled: true vcluster-k0s: helm: releaseName: vcluster chart: - name: ./charts/k0s - values: *values # reuses the same values defined under the &values anchor + name: ./chart + values: + controlPlane: + distro: + k3s: + enabled: true + statefulSet: + image: + repository: image(${SYNCER_IMAGE}) + tag: tag(${SYNCER_IMAGE}) + scheduling: + tolerations: + - operator: "Exists" + service: + spec: + type: NodePort + advanced: + serviceAccount: + enabled: false + name: default + telemetry: + disabled: true # Dev Configuration for vcluster dev: @@ -105,6 +122,7 @@ dev: - 'cmd/vclusterctl/cmd/charts' - '!/cmd' - '!/vendor' + - '!/config' - '!/hack' - '!/go.mod' - '!/go.sum' @@ -158,36 +176,6 @@ pipelines: kubectl delete pvc --all -n ${DEVSPACE_NAMESPACE} profiles: - - name: dev-hostpath-mapper - patches: - - op: remove - path: dev.vcluster.imageSelector - - op: remove - path: dev.vcluster.container - - op: add - path: dev.vcluster.labelSelector - value: - component: hostpath-mapper - merge: - deployments: - vcluster-k3s: &hostmapper_patch - helm: - values: - hostpathMapper: - image: ${SYNCER_IMAGE} - enabled: true - dev: true - syncer: - livenessProbe: - enabled: false - readinessProbe: - enabled: false - extraArgs: - - start - - --tls-san=vcluster - vcluster-k0s: *hostmapper_patch # reuses the same patch defined under the &hostmapper_patch anchor - vcluster-k8s: *hostmapper_patch # reuses the same patch defined under the &hostmapper_patch anchor - - name: test-k3s patches: - op: add diff --git a/generic-sync-examples/cert-manager/config.yaml b/generic-sync-examples/cert-manager/config.yaml index 054b31f0f..ea401f785 100644 --- a/generic-sync-examples/cert-manager/config.yaml +++ b/generic-sync-examples/cert-manager/config.yaml @@ -1,7 +1,8 @@ -multiNamespaceMode: - enabled: true -sync: - generic: +experimental: + multiNamespaceMode: + enabled: true + + genericSync: role: extraRules: - apiGroups: ["cert-manager.io"] @@ -12,13 +13,11 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: cert-manager.io/v1 - kind: Issuer - - apiVersion: cert-manager.io/v1 - kind: Certificate - import: - - kind: Secret - apiVersion: v1 \ No newline at end of file + export: + - apiVersion: cert-manager.io/v1 + kind: Issuer + - apiVersion: cert-manager.io/v1 + kind: Certificate + import: + - kind: Secret + apiVersion: v1 diff --git a/generic-sync-examples/dapr/config.yaml b/generic-sync-examples/dapr/config.yaml index a07b5c755..13ab15643 100644 --- a/generic-sync-examples/dapr/config.yaml +++ b/generic-sync-examples/dapr/config.yaml @@ -1,7 +1,8 @@ -multiNamespaceMode: - enabled: true -sync: - generic: +experimental: + multiNamespaceMode: + enabled: true + + genericSync: clusterRole: extraRules: - apiGroups: [ "apiextensions.k8s.io" ] @@ -10,14 +11,12 @@ sync: - apiGroups: ["dapr.io"] resources: ["components", "configurations", "resiliencies", "subscriptions"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - config: |- - version: v1beta1 - export: - - kind: Component - apiVersion: dapr.io/v1alpha1 - - kind: Configuration - apiVersion: dapr.io/v1alpha1 - - kind: Resiliency - apiVersion: dapr.io/v1alpha1 - - kind: Subscription - apiVersion: dapr.io/v1alpha1 \ No newline at end of file + export: + - kind: Component + apiVersion: dapr.io/v1alpha1 + - kind: Configuration + apiVersion: dapr.io/v1alpha1 + - kind: Resiliency + apiVersion: dapr.io/v1alpha1 + - kind: Subscription + apiVersion: dapr.io/v1alpha1 diff --git a/generic-sync-examples/istio/config.yaml b/generic-sync-examples/istio/config.yaml index 0e5677cb0..a3d7a8ef8 100644 --- a/generic-sync-examples/istio/config.yaml +++ b/generic-sync-examples/istio/config.yaml @@ -22,11 +22,11 @@ coredns: # Traffic to service.namespace.svc(.cluster.local) URLs will not be routed by Istio. # --- -multiNamespaceMode: - enabled: true +experimental: + multiNamespaceMode: + enabled: true -sync: - generic: + genericSync: role: extraRules: - apiGroups: ["networking.istio.io"] @@ -38,29 +38,27 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: networking.istio.io/v1beta1 - kind: Gateway + export: + - apiVersion: networking.istio.io/v1beta1 + kind: Gateway - - apiVersion: networking.istio.io/v1beta1 - kind: VirtualService - patches: - - op: rewriteName - path: .spec.gateways[*] - regex: "($NAMESPACE/)?$NAME" - conditions: - - notEqual: "mesh" + - apiVersion: networking.istio.io/v1beta1 + kind: VirtualService + patches: + - op: rewriteName + path: .spec.gateways[*] + regex: "($NAMESPACE/)?$NAME" + conditions: + - notEqual: "mesh" - - apiVersion: networking.istio.io/v1beta1 - kind: DestinationRule - patches: - - op: rewriteName - path: .spec.exportTo - regex: $NAMESPACE - conditions: - - notEqual: "." - import: - - kind: Secret - apiVersion: v1 \ No newline at end of file + - apiVersion: networking.istio.io/v1beta1 + kind: DestinationRule + patches: + - op: rewriteName + path: .spec.exportTo + regex: $NAMESPACE + conditions: + - notEqual: "." + import: + - kind: Secret + apiVersion: v1 diff --git a/generic-sync-examples/istio/host-only-gateways.yaml b/generic-sync-examples/istio/host-only-gateways.yaml index 7cdd84f02..cb6092b30 100644 --- a/generic-sync-examples/istio/host-only-gateways.yaml +++ b/generic-sync-examples/istio/host-only-gateways.yaml @@ -21,8 +21,8 @@ coredns: # Warning: current configuration supports VirtualService traffic only within the same namespace. # Traffic to service.namespace.svc(.cluster.local) URLs will not be routed by Istio. # -sync: - generic: +experimental: + genericSync: role: extraRules: - apiGroups: ["networking.istio.io"] @@ -34,63 +34,61 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: networking.istio.io/v1alpha3 - kind: VirtualService - patches: - # If you wish to set a specific Gateway for all - # VirtualServices created inside the vcluster then - # replace "your-gateway-here" below with the name of - # that Gateway and uncomment 4 lines below: - # - op: replace - # path: .spec.gateways - # value: - # - your-gateway-here - - op: rewriteName - path: .spec.hosts[*] - regex: &svcHost > - ^$NAME((\.$NAMESPACE)?(\.svc(\.cluster\.local)?){1})?$ - - op: rewriteName - path: .spec.http[*].route[*].destination.host - regex: *svcHost - reversePatches: - - op: copyFromObject - fromPath: status - path: status - - apiVersion: networking.istio.io/v1alpha3 - kind: DestinationRule - patches: - - op: rewriteName - path: .spec.host - regex: *svcHost - - op: rewriteName - path: .spec.trafficPolicy.tls.credentialName - sync: - secret: true - - op: rewriteName - path: .spec.trafficPolicy.portLevelSettings[*].tls.credentialName - sync: - secret: true - - op: rewriteName - path: .spec.trafficPolicy.tunnel.targetHost - regex: *svcHost - - op: rewriteLabelSelector - path: .spec.subsets[*].labels - - op: rewriteName - path: .spec.subsets[*].trafficPolicy.tls.credentialName - sync: - secret: true - - op: rewriteName - path: .spec.subsets[*].trafficPolicy.portLevelSettings[*].tls.credentialName - sync: - secret: true - - op: rewriteName - path: .spec.subsets[*].trafficPolicy.tunnel.targetHost - regex: *svcHost - - op: replace - path: .spec.exportTo - value: "." - - op: rewriteLabelSelector - path: .spec.workloadSelector + export: + - apiVersion: networking.istio.io/v1alpha3 + kind: VirtualService + patches: + # If you wish to set a specific Gateway for all + # VirtualServices created inside the vcluster then + # replace "your-gateway-here" below with the name of + # that Gateway and uncomment 4 lines below: + # - op: replace + # path: .spec.gateways + # value: + # - your-gateway-here + - op: rewriteName + path: .spec.hosts[*] + regex: &svcHost > + ^$NAME((\.$NAMESPACE)?(\.svc(\.cluster\.local)?){1})?$ + - op: rewriteName + path: .spec.http[*].route[*].destination.host + regex: *svcHost + reversePatches: + - op: copyFromObject + fromPath: status + path: status + - apiVersion: networking.istio.io/v1alpha3 + kind: DestinationRule + patches: + - op: rewriteName + path: .spec.host + regex: *svcHost + - op: rewriteName + path: .spec.trafficPolicy.tls.credentialName + sync: + secret: true + - op: rewriteName + path: .spec.trafficPolicy.portLevelSettings[*].tls.credentialName + sync: + secret: true + - op: rewriteName + path: .spec.trafficPolicy.tunnel.targetHost + regex: *svcHost + - op: rewriteLabelSelector + path: .spec.subsets[*].labels + - op: rewriteName + path: .spec.subsets[*].trafficPolicy.tls.credentialName + sync: + secret: true + - op: rewriteName + path: .spec.subsets[*].trafficPolicy.portLevelSettings[*].tls.credentialName + sync: + secret: true + - op: rewriteName + path: .spec.subsets[*].trafficPolicy.tunnel.targetHost + regex: *svcHost + - op: replace + path: .spec.exportTo + value: "." + - op: rewriteLabelSelector + path: .spec.workloadSelector diff --git a/generic-sync-examples/karpenter/config.yaml b/generic-sync-examples/karpenter/config.yaml index a71d81b66..38d90d75d 100644 --- a/generic-sync-examples/karpenter/config.yaml +++ b/generic-sync-examples/karpenter/config.yaml @@ -1,7 +1,8 @@ -multiNamespaceMode: - enabled: true -sync: - generic: +experimental: + multiNamespaceMode: + enabled: true + + genericSync: role: extraRules: - apiGroups: ["karpenter.sh"] @@ -15,11 +16,8 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: karpenter.sh/v1alpha5 - kind: Provisioner - - apiVersion: karpenter.k8s.aws/v1alpha1 - kind: AWSNodeTemplate - \ No newline at end of file + export: + - apiVersion: karpenter.sh/v1alpha5 + kind: Provisioner + - apiVersion: karpenter.k8s.aws/v1alpha1 + kind: AWSNodeTemplate diff --git a/generic-sync-examples/knative/config.yaml b/generic-sync-examples/knative/config.yaml index c8e1243ed..952be0edc 100644 --- a/generic-sync-examples/knative/config.yaml +++ b/generic-sync-examples/knative/config.yaml @@ -1,8 +1,8 @@ -multiNamespaceMode: - enabled: true +experimental: + multiNamespaceMode: + enabled: true -sync: - generic: + genericSync: role: extraRules: - apiGroups: ["serving.knative.dev"] @@ -13,23 +13,21 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: serving.knative.dev/v1 - kind: Service - - apiVersion: serving.knative.dev/v1 - kind: Configuration - - apiVersion: serving.knative.dev/v1 - kind: Revision - - apiVersion: serving.knative.dev/v1 - kind: Route - import: - - apiVersion: serving.knative.dev/v1 - kind: Service - - apiVersion: serving.knative.dev/v1 - kind: Configuration - - apiVersion: serving.knative.dev/v1 - kind: Revision - - apiVersion: serving.knative.dev/v1 - kind: Route \ No newline at end of file + export: + - apiVersion: serving.knative.dev/v1 + kind: Service + - apiVersion: serving.knative.dev/v1 + kind: Configuration + - apiVersion: serving.knative.dev/v1 + kind: Revision + - apiVersion: serving.knative.dev/v1 + kind: Route + import: + - apiVersion: serving.knative.dev/v1 + kind: Service + - apiVersion: serving.knative.dev/v1 + kind: Configuration + - apiVersion: serving.knative.dev/v1 + kind: Revision + - apiVersion: serving.knative.dev/v1 + kind: Route diff --git a/generic-sync-examples/tekton-pipelines/config.yaml b/generic-sync-examples/tekton-pipelines/config.yaml index a142690c0..03b27284c 100644 --- a/generic-sync-examples/tekton-pipelines/config.yaml +++ b/generic-sync-examples/tekton-pipelines/config.yaml @@ -1,34 +1,32 @@ -multiNamespaceMode: - enabled: true +experimental: + multiNamespaceMode: + enabled: true -sync: - generic: + genericSync: role: extraRules: - apiGroups: ["tekton.dev"] resources: ["tasks", "taskruns", "pipelines", "pipelineruns"] verbs: ["create", "delete", "patch", "update", "get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: tekton.dev/v1beta1 - kind: Task - - apiVersion: tekton.dev/v1beta1 - kind: TaskRun - patches: - - op: rewriteName - path: spec.taskRef.name - - apiVersion: tekton.dev/v1beta1 - kind: Pipeline - patches: - - op: rewriteName - path: spec.tasks[*].taskRef.name - - op: rewriteName - path: spec.resources[*].name - - op: rewriteName - path: spec.results[*].name - - apiVersion: tekton.dev/v1beta1 - kind: PipelineRun - patches: - - op: rewriteName - path: spec.pipelineRef.name \ No newline at end of file + export: + - apiVersion: tekton.dev/v1beta1 + kind: Task + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + patches: + - op: rewriteName + path: spec.taskRef.name + - apiVersion: tekton.dev/v1beta1 + kind: Pipeline + patches: + - op: rewriteName + path: spec.tasks[*].taskRef.name + - op: rewriteName + path: spec.resources[*].name + - op: rewriteName + path: spec.results[*].name + - apiVersion: tekton.dev/v1beta1 + kind: PipelineRun + patches: + - op: rewriteName + path: spec.pipelineRef.name diff --git a/generic-sync-examples/traefik/config.yaml b/generic-sync-examples/traefik/config.yaml index f9046b79c..bca3777af 100644 --- a/generic-sync-examples/traefik/config.yaml +++ b/generic-sync-examples/traefik/config.yaml @@ -1,8 +1,9 @@ # NOTE: This config was created with Traefik version v2.9.10 (the version currently shipping with K3s). -multiNamespaceMode: - enabled: true -sync: - generic: +experimental: + multiNamespaceMode: + enabled: true + + genericSync: role: extraRules: # NOTE: If your host cluster uses Traefik v3+, you will need to change "traefik.containo.us" to "traefik.io". @@ -15,12 +16,10 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - export: - - apiVersion: traefik.containo.us/v1alpha1 - kind: IngressRoute - - apiVersion: traefik.containo.us/v1alpha1 - kind: IngressRouteTCP - - apiVersion: traefik.containo.us/v1alpha1 - kind: IngressRouteUDP + export: + - apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + - apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteTCP + - apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP diff --git a/go.mod b/go.mod index 8d91142bb..80ba707ff 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/hashicorp/go-hclog v0.14.1 github.com/hashicorp/go-plugin v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.2 + github.com/invopop/jsonschema v0.12.0 github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 github.com/loft-sh/admin-apis v0.0.0-20240203010124-3600c1c582a8 github.com/loft-sh/agentapi/v3 v3.4.0-beta.12.0.20240207094449-7adc00ae1265 @@ -21,7 +22,6 @@ require ( github.com/loft-sh/api/v3 v3.0.0-20240207094436-f23808d5a185 github.com/loft-sh/loftctl/v3 v3.0.0-20240207094551-f600825cc6f5 github.com/loft-sh/utils v0.0.29 - github.com/loft-sh/vcluster-values v0.0.0-20240207093538-4bbb24e9f699 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d github.com/mitchellh/go-homedir v1.1.0 github.com/moby/locker v1.0.1 @@ -70,6 +70,8 @@ require ( github.com/Sytten/logrus-zap-hook v0.1.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect @@ -98,6 +100,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect @@ -118,7 +121,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/evanphx/json-patch v5.8.1+incompatible + github.com/evanphx/json-patch v5.8.1+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/go.sum b/go.sum index 11e4634ea..71755ae9c 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:W github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -127,6 +129,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= @@ -571,6 +575,8 @@ github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= @@ -643,8 +649,6 @@ github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a h1:/gqqjKpcHEdFXIX41lx github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a/go.mod h1:YImeRjXH34Yf5E79T7UHBQpDZl9fIaaFRgyZ/bkY+UQ= github.com/loft-sh/utils v0.0.29 h1:P/MObccXToAZy2QoJSQDJ+OJx1qHitpFHEVj3QBSNJs= github.com/loft-sh/utils v0.0.29/go.mod h1:9hlX9cGpWHg3mNi/oBlv3X4ePGDMK66k8MbOZGFMDTI= -github.com/loft-sh/vcluster-values v0.0.0-20240207093538-4bbb24e9f699 h1:lbbIlSpl/sz1iY/Tl/VUOU8NtFe31uwvlUbANK04mDM= -github.com/loft-sh/vcluster-values v0.0.0-20240207093538-4bbb24e9f699/go.mod h1:J34xtWyMbjM+NRgVWxO0IVDB5XYUaX52jPQNqEAhu4M= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -897,6 +901,8 @@ github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= diff --git a/hack/assets/main.go b/hack/assets/main.go index dccd51a42..653f783ce 100644 --- a/hack/assets/main.go +++ b/hack/assets/main.go @@ -5,11 +5,10 @@ import ( "os" "strings" + vclusterconfig "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/constants" "github.com/loft-sh/vcluster/pkg/coredns" - "github.com/loft-sh/vcluster/pkg/options" - - "github.com/loft-sh/vcluster-values/values" ) func main() { @@ -17,10 +16,10 @@ func main() { // loft images = append(images, "ghcr.io/loft-sh/vcluster:"+cleanTag(os.Args[1])) - images = append(images, options.DefaultHostsRewriteImage) + images = append(images, config.DefaultHostsRewriteImage) // loop over k3s versions - for _, image := range values.K3SVersionMap { + for _, image := range vclusterconfig.K3SVersionMap { if contains(images, image) { continue } @@ -29,7 +28,7 @@ func main() { } // loop over k0s versions - for _, image := range values.K0SVersionMap { + for _, image := range vclusterconfig.K0SVersionMap { if contains(images, image) { continue } @@ -38,21 +37,21 @@ func main() { } // loop over k8s versions - for _, image := range values.K8SAPIVersionMap { + for _, image := range vclusterconfig.K8SAPIVersionMap { if contains(images, image) { continue } images = append(images, image) } - for _, image := range values.K8SControllerVersionMap { + for _, image := range vclusterconfig.K8SControllerVersionMap { if contains(images, image) { continue } images = append(images, image) } - for _, image := range values.K8SEtcdVersionMap { + for _, image := range vclusterconfig.K8SEtcdVersionMap { if contains(images, image) { continue } diff --git a/hack/compat-matrix/main.go b/hack/compat-matrix/main.go index fb8109f96..b06f4027c 100644 --- a/hack/compat-matrix/main.go +++ b/hack/compat-matrix/main.go @@ -8,10 +8,10 @@ import ( "slices" "strings" + vclusterconfig "github.com/loft-sh/vcluster/config" "github.com/olekukonko/tablewriter" "gopkg.in/yaml.v2" - "github.com/loft-sh/vcluster-values/values" "golang.org/x/exp/maps" ) @@ -57,13 +57,13 @@ func main() { var versionMap map[string]string switch v { case "k3s": - versionMap = values.K3SVersionMap + versionMap = vclusterconfig.K3SVersionMap case "k8s": - versionMap = values.K8SAPIVersionMap + versionMap = vclusterconfig.K8SAPIVersionMap case "k0s": - versionMap = values.K0SVersionMap + versionMap = vclusterconfig.K0SVersionMap case "eks": - versionMap = values.EKSAPIVersionMap + versionMap = vclusterconfig.EKSAPIVersionMap } buff := updateTableWithDistro(v, versionMap, issues) renderedBytes.WriteString(fmt.Sprintf(templateString, v, v, buff.String())) diff --git a/hack/embed-charts.sh b/hack/embed-charts.sh index cdfed4506..aa12b2a20 100755 --- a/hack/embed-charts.sh +++ b/hack/embed-charts.sh @@ -13,7 +13,4 @@ rm -rfv "${EMBED_DIR}" mkdir "${EMBED_DIR}" touch "${EMBED_DIR}/gitkeep.tgz" -for CHART in k3s k8s k0s eks; -do - helm package --version "${RELEASE_VERSION}" "${VCLUSTER_ROOT}/charts/${CHART}" -d "${EMBED_DIR}"; -done +helm package --version "${RELEASE_VERSION}" "${VCLUSTER_ROOT}/chart" -d "${EMBED_DIR}"; diff --git a/hack/schema/main.go b/hack/schema/main.go new file mode 100644 index 000000000..b8993dcd8 --- /dev/null +++ b/hack/schema/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/invopop/jsonschema" + "github.com/loft-sh/vcluster/config" +) + +const OutFile = "chart/values.schema.json" + +// Run executes the command logic +func main() { + generatedSchema, err := generateSchema(&config.Config{}) + if err != nil { + panic(err) + } + + transformMapProperties(generatedSchema) + modifySchema(generatedSchema, cleanUp) + err = writeSchema(generatedSchema, OutFile) + if err != nil { + panic(err) + } +} + +func generateSchema(configInstance interface{}) (*jsonschema.Schema, error) { + r := new(jsonschema.Reflector) + r.RequiredFromJSONSchemaTags = true + r.BaseSchemaID = "https://vcluster.com/schemas" + r.ExpandedStruct = true + + commentMap := map[string]string{} + + err := jsonschema.ExtractGoComments("github.com/loft-sh/vcluster", "config", commentMap) + if err != nil { + return nil, err + } + + r.CommentMap = commentMap + + return r.Reflect(configInstance), nil +} + +func writeSchema(schema *jsonschema.Schema, schemaFile string) error { + prefix := "" + schemaString, err := json.MarshalIndent(schema, prefix, " ") + if err != nil { + return err + } + + err = os.MkdirAll(filepath.Dir(schemaFile), os.ModePerm) + if err != nil { + return err + } + + err = os.WriteFile(schemaFile, schemaString, os.ModePerm) + if err != nil { + return err + } + + return nil +} + +func modifySchema(schema *jsonschema.Schema, visitors ...func(s *jsonschema.Schema)) { + // Apply visitors + if len(visitors) > 0 { + for _, visitor := range visitors { + walk(schema, visitor) + } + } +} + +func transformMapProperties(s *jsonschema.Schema) { + plugins, ok := s.Properties.Get("plugins") + if ok { + plugins.AnyOf = modifyAnyOf(plugins) + plugins.PatternProperties = nil + } + + plugin, ok := s.Properties.Get("plugin") + if ok { + plugin.AnyOf = modifyAnyOf(plugin) + plugin.PatternProperties = nil + } +} + +func modifyAnyOf(field interface{}) []*jsonschema.Schema { + return []*jsonschema.Schema{ + { + Type: "object", + PatternProperties: map[string]*jsonschema.Schema{ + ".*": { + Type: "string", + }, + }, + }, + { + Type: "object", + PatternProperties: field.(*jsonschema.Schema).PatternProperties, + }, + { + Type: "object", + }, + } +} + +func cleanUp(s *jsonschema.Schema) { + if len(s.OneOf) > 0 || len(s.AnyOf) > 0 { + s.Ref = "" + s.Type = "" + s.Items = nil + s.PatternProperties = nil + } +} + +func walk(schema *jsonschema.Schema, visit func(s *jsonschema.Schema)) { + for pair := schema.Properties.Oldest(); pair != nil; pair = pair.Next() { + visit(pair.Value) + } + + for _, definition := range schema.Definitions { + for pair := definition.Properties.Oldest(); pair != nil; pair = pair.Next() { + visit(pair.Value) + } + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index c1b8f0ce9..2a1a319b5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,155 +1,211 @@ package config import ( - "regexp" -) + "strings" -const Version = "v1beta1" + "github.com/loft-sh/vcluster/config" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" + "k8s.io/klog/v2" +) -type Config struct { - // Version is the config version - Version string `json:"version,omitempty" yaml:"version,omitempty"` +// VirtualClusterConfig wraps the config and adds extra info such as name, serviceName and targetNamespace +type VirtualClusterConfig struct { + // Holds the vCluster config + config.Config `json:",inline"` - // Exports syncs a resource from the virtual cluster to the host - Exports []*Export `json:"export,omitempty" yaml:"export,omitempty"` + // Name is the name of the vCluster + Name string `json:"name"` - // Imports syncs a resource from the host cluster to virtual cluster - Imports []*Import `json:"import,omitempty" yaml:"import,omitempty"` + // ServiceName is the name of the service of the vCluster + ServiceName string `json:"serviceName,omitempty"` - // Hooks are hooks that can be used to inject custom patches before syncing - Hooks *Hooks `json:"hooks,omitempty" yaml:"hooks,omitempty"` + // TargetNamespace is the namespace where the workloads go + TargetNamespace string `json:"targetNamespace,omitempty"` } -type Hooks struct { - // HostToVirtual is a hook that is executed before syncing from the host to the virtual cluster - HostToVirtual []*Hook `json:"hostToVirtual,omitempty" yaml:"hostToVirtual,omitempty"` - - // VirtualToHost is a hook that is executed before syncing from the virtual to the host cluster - VirtualToHost []*Hook `json:"virtualToHost,omitempty" yaml:"virtualToHost,omitempty"` +func (v VirtualClusterConfig) Distro() string { + if v.Config.ControlPlane.Distro.K3S.Enabled { + return config.K3SDistro + } else if v.Config.ControlPlane.Distro.K0S.Enabled { + return config.K0SDistro + } else if v.Config.ControlPlane.Distro.K8S.Enabled { + return config.K8SDistro + } else if v.Config.ControlPlane.Distro.EKS.Enabled { + return config.EKSDistro + } + + return config.K3SDistro } -type Hook struct { - TypeInformation - - // Verbs are the verbs that the hook should mutate - Verbs []string `json:"verbs,omitempty" yaml:"verbs,omitempty"` - - // Patches are the patches to apply on the object to be synced - Patches []*Patch `json:"patches,omitempty" yaml:"patches,omitempty"` +func (v VirtualClusterConfig) VirtualClusterKubeConfig() config.VirtualClusterKubeConfig { + distroConfig := config.VirtualClusterKubeConfig{} + switch v.Distro() { + case config.K3SDistro: + distroConfig = config.VirtualClusterKubeConfig{ + KubeConfig: "/data/server/cred/admin.kubeconfig", + ServerCAKey: "/data/server/tls/server-ca.key", + ServerCACert: "/data/server/tls/server-ca.crt", + ClientCACert: "/data/server/tls/client-ca.crt", + RequestHeaderCACert: "/data/server/tls/request-header-ca.crt", + } + case config.K0SDistro: + distroConfig = config.VirtualClusterKubeConfig{ + KubeConfig: "/data/k0s/pki/admin.conf", + ServerCAKey: "/data/k0s/pki/ca.key", + ServerCACert: "/data/k0s/pki/ca.crt", + ClientCACert: "/data/k0s/pki/ca.crt", + RequestHeaderCACert: "/data/k0s/pki/front-proxy-ca.crt", + } + case config.EKSDistro, config.K8SDistro: + distroConfig = config.VirtualClusterKubeConfig{ + KubeConfig: "/pki/admin.conf", + ServerCAKey: "/pki/ca.key", + ServerCACert: "/pki/ca.crt", + ClientCACert: "/pki/ca.crt", + RequestHeaderCACert: "/pki/front-proxy-ca.crt", + } + } + + retConfig := v.Config.Experimental.VirtualClusterKubeConfig + if retConfig.KubeConfig == "" { + retConfig.KubeConfig = distroConfig.KubeConfig + } + if retConfig.ClientCACert == "" { + retConfig.ClientCACert = distroConfig.ClientCACert + } + if retConfig.ServerCAKey == "" { + retConfig.ServerCAKey = distroConfig.ServerCAKey + } + if retConfig.ServerCACert == "" { + retConfig.ServerCACert = distroConfig.ServerCACert + } + if retConfig.RequestHeaderCACert == "" { + retConfig.RequestHeaderCACert = distroConfig.RequestHeaderCACert + } + + return retConfig } -type Import struct { - SyncBase `json:",inline" yaml:",inline"` +// LegacyOptions converts the config to the legacy cluster options +func (v VirtualClusterConfig) LegacyOptions() (*LegacyVirtualClusterOptions, error) { + legacyPlugins := []string{} + for pluginName, plugin := range v.Plugin { + if plugin.Version != "" && !plugin.Optional { + continue + } + + legacyPlugins = append(legacyPlugins, pluginName) + } + + nodeSelector := "" + if v.Sync.FromHost.Nodes.Enabled { + selectors := []string{} + for k, v := range v.Sync.FromHost.Nodes.Selector.Labels { + selectors = append(selectors, k+"="+v) + } + + nodeSelector = strings.Join(selectors, ",") + } + + return &LegacyVirtualClusterOptions{ + ProOptions: LegacyVirtualClusterProOptions{ + RemoteKubeConfig: v.Experimental.IsolatedControlPlane.KubeConfig, + RemoteNamespace: v.Experimental.IsolatedControlPlane.Namespace, + RemoteServiceName: v.Experimental.IsolatedControlPlane.Service, + IntegratedCoredns: v.ControlPlane.CoreDNS.Embedded, + EtcdReplicas: int(v.ControlPlane.StatefulSet.HighAvailability.Replicas), + EtcdEmbedded: v.ControlPlane.BackingStore.EmbeddedEtcd.Enabled, + NoopSyncer: !v.Experimental.SyncSettings.DisableSync, + SyncKubernetesService: v.Experimental.SyncSettings.RewriteKubernetesService, + }, + ServerCaCert: v.VirtualClusterKubeConfig().ServerCACert, + ServerCaKey: v.VirtualClusterKubeConfig().ServerCAKey, + TLSSANs: v.ControlPlane.Proxy.ExtraSANs, + RequestHeaderCaCert: v.VirtualClusterKubeConfig().RequestHeaderCACert, + ClientCaCert: v.VirtualClusterKubeConfig().ClientCACert, + KubeConfigPath: v.VirtualClusterKubeConfig().KubeConfig, + KubeConfigContextName: v.ExportKubeConfig.Context, + KubeConfigSecret: v.ExportKubeConfig.Secret.Name, + KubeConfigSecretNamespace: v.ExportKubeConfig.Secret.Namespace, + KubeConfigServer: v.ExportKubeConfig.Server, + Tolerations: v.Sync.ToHost.Pods.EnforceTolerations, + BindAddress: v.ControlPlane.Proxy.BindAddress, + Port: v.ControlPlane.Proxy.Port, + Name: v.Name, + TargetNamespace: v.TargetNamespace, + ServiceName: v.ServiceName, + SetOwner: v.Experimental.SyncSettings.SetOwner, + SyncAllNodes: v.Sync.FromHost.Nodes.Selector.All, + EnableScheduler: v.ControlPlane.Advanced.VirtualScheduler.Enabled, + DisableFakeKubelets: !v.Networking.Advanced.ProxyKubelets.ByIP && !v.Networking.Advanced.ProxyKubelets.ByHostname, + FakeKubeletIPs: v.Networking.Advanced.ProxyKubelets.ByIP, + ClearNodeImages: v.Sync.FromHost.Nodes.ClearImageStatus, + NodeSelector: nodeSelector, + ServiceAccount: v.ControlPlane.Advanced.WorkloadServiceAccount.Name, + EnforceNodeSelector: true, + PluginListenAddress: "localhost:10099", + OverrideHosts: v.Sync.ToHost.Pods.RewriteHosts.Enabled, + OverrideHostsContainerImage: v.Sync.ToHost.Pods.RewriteHosts.InitContainerImage, + ServiceAccountTokenSecrets: v.Sync.ToHost.Pods.UseSecretsForSATokens, + ClusterDomain: v.Networking.Advanced.ClusterDomain, + LeaderElect: v.ControlPlane.StatefulSet.HighAvailability.Replicas > 1, + LeaseDuration: v.ControlPlane.StatefulSet.HighAvailability.LeaseDuration, + RenewDeadline: v.ControlPlane.StatefulSet.HighAvailability.RenewDeadline, + RetryPeriod: v.ControlPlane.StatefulSet.HighAvailability.RetryPeriod, + Plugins: legacyPlugins, + DefaultImageRegistry: v.ControlPlane.Advanced.DefaultImageRegistry, + EnforcePodSecurityStandard: v.Policies.PodSecurityStandard, + SyncLabels: v.Experimental.SyncSettings.SyncLabels, + MountPhysicalHostPaths: false, + HostMetricsBindAddress: "0", + VirtualMetricsBindAddress: "0", + MultiNamespaceMode: v.Experimental.MultiNamespaceMode.Enabled, + SyncAllSecrets: v.Sync.ToHost.Secrets.All, + SyncAllConfigMaps: v.Sync.ToHost.ConfigMaps.All, + ProxyMetricsServer: v.Observability.Metrics.Proxy.Nodes || v.Observability.Metrics.Proxy.Pods, + + DeprecatedSyncNodeChanges: v.Sync.FromHost.Nodes.SyncBackChanges, + }, nil } -type SyncBase struct { - TypeInformation `json:",inline" yaml:",inline"` - - Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"` - - // ReplaceWhenInvalid determines if the controller should try to recreate the object - // if there is a problem applying - ReplaceWhenInvalid bool `json:"replaceOnConflict,omitempty" yaml:"replaceOnConflict,omitempty"` - - // Patches are the patches to apply on the virtual cluster objects - // when syncing them from the host cluster - Patches []*Patch `json:"patches,omitempty" yaml:"patches,omitempty"` - - // ReversePatches are the patches to apply to host cluster objects - // after it has been synced to the virtual cluster - ReversePatches []*Patch `json:"reversePatches,omitempty" yaml:"reversePatches,omitempty"` +// DisableMissingAPIs checks if the apis are enabled, if any are missing, disable the syncer and print a log +func (v VirtualClusterConfig) DisableMissingAPIs(discoveryClient discovery.DiscoveryInterface) error { + resources, err := discoveryClient.ServerResourcesForGroupVersion("storage.k8s.io/v1") + if err != nil && !kerrors.IsNotFound(err) { + return err + } + + // check if found + if v.Sync.FromHost.CSINodes.Enabled && !findResource(resources, "csinodes") { + v.Sync.FromHost.CSINodes.Enabled = false + klog.Warningf("host kubernetes apiserver not advertising resource csinodes in GroupVersion storage.k8s.io/v1, disabling the syncer") + } + + // check if found + if v.Sync.FromHost.CSIDrivers.Enabled && !findResource(resources, "csidrivers") { + v.Sync.FromHost.CSIDrivers.Enabled = false + klog.Warningf("host kubernetes apiserver not advertising resource csidrivers in GroupVersion storage.k8s.io/v1, disabling the syncer") + } + + // check if found + if v.Sync.FromHost.CSIStorageCapacities.Enabled && !findResource(resources, "csistoragecapacities") { + v.Sync.FromHost.CSIStorageCapacities.Enabled = false + klog.Warningf("host kubernetes apiserver not advertising resource csistoragecapacities in GroupVersion storage.k8s.io/v1, disabling the syncer") + } + + return nil } -type Export struct { - SyncBase `json:",inline" yaml:",inline"` - - // Selector is a label selector to select the synced objects in the virtual cluster. - // If empty, all objects will be synced. - Selector *Selector `json:"selector,omitempty" yaml:"selector,omitempty"` -} - -type TypeInformation struct { - // APIVersion of the object to sync - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - - // Kind of the object to sync - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` -} - -type Selector struct { - // LabelSelector are the labels to select the object from - LabelSelector map[string]string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"` -} - -type Patch struct { - // Operation is the type of the patch - Operation PatchType `json:"op,omitempty" yaml:"op,omitempty"` - - // FromPath is the path from the other object - FromPath string `json:"fromPath,omitempty" yaml:"fromPath,omitempty"` - - // Path is the path of the patch - Path string `json:"path,omitempty" yaml:"path,omitempty"` - - // NamePath is the path to the name of a child resource within Path - NamePath string `json:"namePath,omitempty" yaml:"namePath,omitempty"` - - // NamespacePath is path to the namespace of a child resource within Path - NamespacePath string `json:"namespacePath,omitempty" yaml:"namespacePath,omitempty"` - - // Value is the new value to be set to the path - Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` - - // Regex - is regular expresion used to identify the Name, - // and optionally Namespace, parts of the field value that - // will be replaced with the rewritten Name and/or Namespace - Regex string `json:"regex,omitempty" yaml:"regex,omitempty"` - ParsedRegex *regexp.Regexp `json:"-" yaml:"-"` - - // Conditions are conditions that must be true for - // the patch to get executed - Conditions []*PatchCondition `json:"conditions,omitempty" yaml:"conditions,omitempty"` - - // Ignore determines if the path should be ignored if handled as a reverse patch - Ignore *bool `json:"ignore,omitempty" yaml:"ignore,omitempty"` - - // Sync defines if a specialized syncer should be initialized using values - // from the rewriteName operation as Secret/Configmap names to be synced - Sync *PatchSync `json:"sync,omitempty" yaml:"sync,omitempty"` -} - -type PatchType string - -const ( - PatchTypeRewriteName = "rewriteName" - PatchTypeRewriteLabelKey = "rewriteLabelKey" - PatchTypeRewriteLabelSelector = "rewriteLabelSelector" - PatchTypeRewriteLabelExpressionsSelector = "rewriteLabelExpressionsSelector" - - PatchTypeCopyFromObject = "copyFromObject" - PatchTypeAdd = "add" - PatchTypeReplace = "replace" - PatchTypeRemove = "remove" -) - -type PatchCondition struct { - // Path is the path within the object to select - Path string `json:"path,omitempty" yaml:"path,omitempty"` - - // SubPath is the path below the selected object to select - SubPath string `json:"subPath,omitempty" yaml:"subPath,omitempty"` - - // Equal is the value the path should be equal to - Equal interface{} `json:"equal,omitempty" yaml:"equal,omitempty"` - - // NotEqual is the value the path should not be equal to - NotEqual interface{} `json:"notEqual,omitempty" yaml:"notEqual,omitempty"` - - // Empty means that the path value should be empty or unset - Empty *bool `json:"empty,omitempty" yaml:"empty,omitempty"` -} +func findResource(resources *metav1.APIResourceList, resourcePlural string) bool { + if resources != nil { + for _, r := range resources.APIResources { + if r.Name == resourcePlural { + return true + } + } + } -type PatchSync struct { - Secret *bool `json:"secret,omitempty" yaml:"secret,omitempty"` - ConfigMap *bool `json:"configmap,omitempty" yaml:"configmap,omitempty"` + return false } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100644 index 299ccf3c5..000000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package config - -import ( - "strings" - "testing" -) - -func TestConfigParsing(t *testing.T) { - rawConfig := `version: v1beta1 -export: # Old from virtual cluster -- apiVersion: cert-manager.io/v1 - kind: Issuer -- apiVersion: cert-manager.io/v1 - kind: Certificates - patches: - - op: rewriteName - path: spec.ca.secretName -- apiVersion: monitoring.coreos.com/v1 - kind: ServiceMonitor - patches: - - op: add - path: .metadata.labels - value: - prometheus-instance: default-instance - - op: copyFromObject - fromPath: .metadata.labels['prometheus-instance'] - path: .metadata.labels['prometheus-instance'] - - op: replace - path: .spec.namespaceSelector - value: - any: false - matchNames: [] - - op: rewriteName - path: .spec.endpoints[*] - - op: rewriteLabelKey - path: .spec.jobLabel - - op: rewriteLabelKey - path: .spec.targetLabels[*] - - op: rewriteLabelKey - path: .spec.podTargetLabels[*] - - op: rewriteLabelExpressionsSelector - path: .spec.selector - reversePatches: - - op: copyFromObject # Sync status back by default - fromPath: status - path: status` - - _, err := Parse(rawConfig) - if err != nil { - t.Fatalf("Error parsing config %v", err) - } -} - -func TestConfigParsingHooks(t *testing.T) { - rawConfig := `version: v1beta1 -hooks: - hostToVirtual: - - apiVersion: v1 - kind: Pod - verbs: ["create", "update", "patch"] - patches: - - op: add - path: metadata.annotations - value: - import-annotation: testing-annotation-import - virtualToHost: - - apiVersion: v1 - kind: Pod - verbs: ["create", "update", "patch"] - patches: - - op: add - path: metadata.annotations - value: - export-annotation: testing-annotation-export -` - - _, err := Parse(rawConfig) - if err != nil { - t.Fatalf("Error parsing config %v", err) - } -} - -func TestConfigParsingHooksUnknownVerb(t *testing.T) { - rawConfig := `version: v1beta1 -hooks: - hostToVirtual: - - apiVersion: v1 - kind: Pod - verbs: ["create", "update", "patch", "unknown"] - patches: - - op: add - path: metadata.annotations - value: - import-annotation: testing-annotation-import - virtualToHost: - - apiVersion: v1 - kind: Pod - verbs: ["create", "update", "patch"] - patches: - - op: add - path: metadata.annotations - value: - export-annotation: testing-annotation-export -` - - _, err := Parse(rawConfig) - if err == nil { - t.Fatalf("Error parsing config %v", err) - } - - if !strings.Contains(err.Error(), "invalid verb \"unknown\";") { - t.Fatalf("Error parsing config %v", err) - } -} diff --git a/pkg/options/controller_context.go b/pkg/config/controller_context.go similarity index 76% rename from pkg/options/controller_context.go rename to pkg/config/controller_context.go index cabf1455e..cccb2cb7c 100644 --- a/pkg/options/controller_context.go +++ b/pkg/config/controller_context.go @@ -1,11 +1,10 @@ -package options +package config import ( "context" "net/http" servertypes "github.com/loft-sh/vcluster/pkg/server/types" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/version" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ctrl "sigs.k8s.io/controller-runtime" @@ -23,11 +22,10 @@ type ControllerContext struct { CurrentNamespace string CurrentNamespaceClient client.Client - Controllers sets.Set[string] AdditionalServerFilters []servertypes.Filter - Options *VirtualClusterOptions + Config *VirtualClusterConfig StopChan <-chan struct{} - //set of extra services that should handle the traffic or pass it along + // set of extra services that should handle the traffic or pass it along ExtraHandlers []func(http.Handler) http.Handler } diff --git a/pkg/config/coredns_plugin_validation.go b/pkg/config/coredns_plugin_validation.go new file mode 100644 index 000000000..fa2e5fe48 --- /dev/null +++ b/pkg/config/coredns_plugin_validation.go @@ -0,0 +1,83 @@ +package config + +import ( + "fmt" + "strings" + + vclusterconfig "github.com/loft-sh/vcluster/config" +) + +func validateMappings(resolveDNS []vclusterconfig.ResolveDNS) error { + for i, mapping := range resolveDNS { + // parse service format + options := 0 + if mapping.Service != "" { + options++ + if strings.Count(mapping.Service, "/") != 1 { + return fmt.Errorf("error validating networking.resolveDNS[%d].service: expected format namespace/name, but got %s", i, mapping.Service) + } + } + if mapping.Hostname != "" { + options++ + } + if mapping.Namespace != "" { + if mapping.Target.HostNamespace == "" { + return fmt.Errorf("error validating networking.resolveDNS[%d].namespace: when using namespace, target.hostNamespace is required", i) + } + + options++ + } else if mapping.Target.HostNamespace != "" { + return fmt.Errorf("error validating networking.resolveDNS[%d]: when using target.hostNamespace, .namespace is required", i) + } + + if options == 0 { + return fmt.Errorf("at least one option required for networking.resolveDNS[%d]", i) + } else if options > 1 { + return fmt.Errorf("only a single option allowed for networking.resolveDNS[%d]", i) + } + + // validate targets + err := validateTarget(mapping.Target) + if err != nil { + return fmt.Errorf("error validating networking.resolveDNS[%d].to", i) + } + } + + return nil +} + +func validateTarget(target vclusterconfig.ResolveDNSTarget) error { + options := 0 + if target.Hostname != "" { + options++ + } + if target.IP != "" { + options++ + } + if target.HostNamespace != "" { + options++ + } + if target.HostService != "" { + options++ + + // check if service is defined with the namespace/name format + if strings.Count(target.HostService, "/") != 1 { + return fmt.Errorf("expected namespace/name format for .to.service, but got %s", target.HostService) + } + } + if target.VClusterService != "" { + options++ + + // check if vcluster service is defined with namespace/name format + if strings.Count(target.VClusterService, "/") != 3 { + return fmt.Errorf("expected hostNamespace/vClusterName/vClusterNamespace/vClusterService format for .to.vClusterService, but got %s", target.VClusterService) + } + } + if options == 0 { + return fmt.Errorf("at least one option required for .to") + } else if options > 1 { + return fmt.Errorf("only a single option allowed for .to") + } + + return nil +} diff --git a/pkg/config/helmvalues/values_test.go b/pkg/config/helmvalues/values_test.go deleted file mode 100644 index 9e0783fe5..000000000 --- a/pkg/config/helmvalues/values_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package helmvalues - -import ( - "os" - "testing" - - "github.com/loft-sh/vcluster-values/helmvalues" - "k8s.io/apimachinery/pkg/util/yaml" -) - -func TestValues(t *testing.T) { - testCases := map[string]string{ - "k0s": "../../../charts/k0s/values.yaml", - "k3s": "../../../charts/k3s/values.yaml", - "k8s": "../../../charts/k8s/values.yaml", - "eks": "../../../charts/eks/values.yaml", - } - for name, path := range testCases { - t.Run(name, func(t *testing.T) { - t.Logf("testing values for %s", name) - sourceValuesRaw, err := os.ReadFile(path) - if err != nil { - t.Fatalf("error reading source values files %v", err) - } - - var helmValues interface{} - - switch name { - case "k0s": - helmValues = &helmvalues.K0s{} - case "k3s": - helmValues = &helmvalues.K3s{} - case "k8s", "eks": - helmValues = &helmvalues.K8s{} - } - - err = yaml.UnmarshalStrict(sourceValuesRaw, helmValues) - if err != nil { - t.Fatalf("error unmarshalling raw values %s", err) - } - }) - } -} diff --git a/pkg/options/flags.go b/pkg/config/legacy_options.go similarity index 62% rename from pkg/options/flags.go rename to pkg/config/legacy_options.go index f002d6406..1b1cda63f 100644 --- a/pkg/options/flags.go +++ b/pkg/config/legacy_options.go @@ -1,9 +1,118 @@ -package options +package config -import ( - "github.com/spf13/pflag" +const ( + DefaultHostsRewriteImage = "library/alpine:3.13.1" ) +// LegacyVirtualClusterOptions holds the cmd flags +type LegacyVirtualClusterOptions struct { + // PRO Options + ProOptions LegacyVirtualClusterProOptions `json:",inline"` + + ServerCaCert string `json:"serverCaCert,omitempty"` + ServerCaKey string `json:"serverCaKey,omitempty"` + TLSSANs []string `json:"tlsSans,omitempty"` + RequestHeaderCaCert string `json:"requestHeaderCaCert,omitempty"` + ClientCaCert string `json:"clientCaCert,omitempty"` + KubeConfigPath string `json:"kubeConfig,omitempty"` + + KubeConfigContextName string `json:"kubeConfigContextName,omitempty"` + KubeConfigSecret string `json:"kubeConfigSecret,omitempty"` + KubeConfigSecretNamespace string `json:"kubeConfigSecretNamespace,omitempty"` + KubeConfigServer string `json:"kubeConfigServer,omitempty"` + Tolerations []string `json:"tolerations,omitempty"` + + BindAddress string `json:"bindAddress,omitempty"` + Port int `json:"port,omitempty"` + + Name string `json:"name,omitempty"` + + TargetNamespace string `json:"targetNamespace,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + + SetOwner bool `json:"setOwner,omitempty"` + + SyncAllNodes bool `json:"syncAllNodes,omitempty"` + EnableScheduler bool `json:"enableScheduler,omitempty"` + DisableFakeKubelets bool `json:"disableFakeKubelets,omitempty"` + FakeKubeletIPs bool `json:"fakeKubeletIPs,omitempty"` + ClearNodeImages bool `json:"clearNodeImages,omitempty"` + + NodeSelector string `json:"nodeSelector,omitempty"` + EnforceNodeSelector bool `json:"enforceNodeSelector,omitempty"` + ServiceAccount string `json:"serviceAccount,omitempty"` + + OverrideHosts bool `json:"overrideHosts,omitempty"` + OverrideHostsContainerImage string `json:"overrideHostsContainerImage,omitempty"` + + ClusterDomain string `json:"clusterDomain,omitempty"` + + LeaderElect bool `json:"leaderElect,omitempty"` + LeaseDuration int `json:"leaseDuration,omitempty"` + RenewDeadline int `json:"renewDeadline,omitempty"` + RetryPeriod int `json:"retryPeriod,omitempty"` + + PluginListenAddress string `json:"pluginListenAddress,omitempty"` + Plugins []string `json:"plugins,omitempty"` + + DefaultImageRegistry string `json:"defaultImageRegistry,omitempty"` + + EnforcePodSecurityStandard string `json:"enforcePodSecurityStandard,omitempty"` + + SyncLabels []string `json:"syncLabels,omitempty"` + + // hostpath mapper options + // this is only needed if using vcluster-hostpath-mapper component + // see: https://github.com/loft-sh/vcluster-hostpath-mapper + MountPhysicalHostPaths bool `json:"mountPhysicalHostPaths,omitempty"` + + HostMetricsBindAddress string `json:"hostMetricsBindAddress,omitempty"` + VirtualMetricsBindAddress string `json:"virtualMetricsBindAddress,omitempty"` + + MultiNamespaceMode bool `json:"multiNamespaceMode,omitempty"` + SyncAllSecrets bool `json:"syncAllSecrets,omitempty"` + SyncAllConfigMaps bool `json:"syncAllConfigMaps,omitempty"` + + ProxyMetricsServer bool `json:"proxyMetricsServer,omitempty"` + ServiceAccountTokenSecrets bool `json:"serviceAccountTokenSecrets,omitempty"` + + // DEPRECATED FLAGS + DeprecatedSyncNodeChanges bool `json:"syncNodeChanges"` +} + +type LegacyVirtualClusterProOptions struct { + RemoteKubeConfig string `json:"remoteKubeConfig,omitempty"` + RemoteNamespace string `json:"remoteNamespace,omitempty"` + RemoteServiceName string `json:"remoteServiceName,omitempty"` + EtcdReplicas int `json:"etcdReplicas,omitempty"` + IntegratedCoredns bool `json:"integratedCoreDNS,omitempty"` + EtcdEmbedded bool `json:"etcdEmbedded,omitempty"` + + NoopSyncer bool `json:"noopSyncer,omitempty"` + SyncKubernetesService bool `json:"synck8sService,omitempty"` +} + +/* +func AddProFlags(flags *pflag.FlagSet, options *VirtualClusterOptions) { + flags.StringVar(&options.ProOptions.ProLicenseSecret, "pro-license-secret", "", "If set, vCluster.Pro will try to find this secret to retrieve the vCluster.Pro license.") + + flags.StringVar(&options.ProOptions.RemoteKubeConfig, "remote-kube-config", "", "If set, will use the remote kube-config instead of the local in-cluster one. Expects a kube config to a headless vcluster installation") + flags.StringVar(&options.ProOptions.RemoteNamespace, "remote-namespace", "", "If set, will use this as the remote namespace") + flags.StringVar(&options.ProOptions.RemoteServiceName, "remote-service-name", "", "If set, will use this as the remote service name") + + flags.BoolVar(&options.ProOptions.IntegratedCoredns, "integrated-coredns", false, "If enabled vcluster will spin an in memory coreDNS inside the syncer container") + flags.BoolVar(&options.ProOptions.UseCoreDNSPlugin, "use-coredns-plugin", false, "If enabled, the vcluster plugin for coredns will be used") + flags.BoolVar(&options.ProOptions.NoopSyncer, "noop-syncer", false, "If enabled will setup a noop Syncer that filters and proxies requests to a specified remote cluster") + flags.BoolVar(&options.ProOptions.SyncKubernetesService, "sync-k8s-service", false, "If enabled will sync the kubernetes service endpoints in the remote cluster with the load balancer ip of this cluster") + + flags.BoolVar(&options.ProOptions.EtcdEmbedded, "etcd-embedded", false, "If true, will start an embedded etcd within vCluster") + flags.StringVar(&options.ProOptions.MigrateFrom, "migrate-from", "", "The url (including protocol) of the original database") + flags.IntVar(&options.ProOptions.EtcdReplicas, "etcd-replicas", 0, "The amount of replicas the etcd has") + + flags.StringArrayVar(&options.ProOptions.EnforceValidatingHooks, "enforce-validating-hook", nil, "A validating hook configuration in yaml format encoded with base64. Can be used multiple times") + flags.StringArrayVar(&options.ProOptions.EnforceMutatingHooks, "enforce-mutating-hook", nil, "A mutating hook configuration in yaml format encoded with base64. Can be used multiple times") +} + func AddFlags(flags *pflag.FlagSet, options *VirtualClusterOptions) { flags.StringVar(&options.KubeConfigContextName, "kube-config-context-name", "", "If set, will override the context name of the generated virtual cluster kube config with this name") flags.StringSliceVar(&options.Controllers, "sync", []string{}, "A list of sync controllers to enable. 'foo' enables the sync controller named 'foo', '-foo' disables the sync controller named 'foo'") @@ -83,4 +192,4 @@ func AddFlags(flags *pflag.FlagSet, options *VirtualClusterOptions) { flags.StringVar(&options.DeprecatedSuffix, "suffix", "", "DEPRECATED: use --name instead") flags.StringVar(&options.DeprecatedOwningStatefulSet, "owning-statefulset", "", "DEPRECATED: use --set-owner instead") flags.StringVar(&options.DeprecatedDisableSyncResources, "disable-sync-resources", "", "DEPRECATED: use --sync instead") -} +}*/ diff --git a/pkg/config/parse.go b/pkg/config/parse.go index 9d0ac29f4..393e5608a 100644 --- a/pkg/config/parse.go +++ b/pkg/config/parse.go @@ -2,189 +2,84 @@ package config import ( "fmt" + "os" - "github.com/samber/lo" + "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/strvals" + "github.com/pkg/errors" "sigs.k8s.io/yaml" ) -var ( - verbs = []string{"get", "list", "create", "update", "patch", "watch", "delete", "deletecollection"} -) - -func Parse(rawConfig string) (*Config, error) { - c := &Config{} - err := yaml.UnmarshalStrict([]byte(rawConfig), c) - if err != nil { - return nil, err +func ParseConfig(path, name string, setValues []string) (*VirtualClusterConfig, error) { + // check if name is empty + if name == "" { + return nil, fmt.Errorf("empty vCluster name") } - err = validate(c) + // read config file + rawFile, err := os.ReadFile(path) if err != nil { return nil, err } - return c, nil -} - -func validate(config *Config) error { - if config.Version != Version { - return fmt.Errorf("unsupported configuration version. Only %s is supported currently", Version) - } - - err := validateExportDuplicates(config.Exports) + // apply set values + rawFile, err = applySetValues(rawFile, setValues) if err != nil { - return err + return nil, fmt.Errorf("apply set values: %w", err) } - for idx, exp := range config.Exports { - if exp == nil { - return fmt.Errorf("exports[%d] is required", idx) - } - - if exp.Kind == "" { - return fmt.Errorf("exports[%d].kind is required", idx) - } - - if exp.APIVersion == "" { - return fmt.Errorf("exports[%d].APIVersion is required", idx) - } - - for patchIdx, patch := range exp.Patches { - err := validatePatch(patch) - if err != nil { - return fmt.Errorf("invalid exports[%d].patches[%d]: %w", idx, patchIdx, err) - } - } - - for patchIdx, patch := range exp.ReversePatches { - err := validatePatch(patch) - if err != nil { - return fmt.Errorf("invalid exports[%d].reversPatches[%d]: %w", idx, patchIdx, err) - } - } - } - - err = validateImportDuplicates(config.Imports) + // create a new strict decoder + rawConfig := &config.Config{} + err = yaml.UnmarshalStrict(rawFile, rawConfig) if err != nil { - return err + fmt.Printf("%#+v\n", errors.Unwrap(err)) + return nil, err } - for idx, imp := range config.Imports { - if imp == nil { - return fmt.Errorf("imports[%d] is required", idx) - } - - if imp.Kind == "" { - return fmt.Errorf("imports[%d].kind is required", idx) - } - - if imp.APIVersion == "" { - return fmt.Errorf("imports[%d].APIVersion is required", idx) - } - - for patchIdx, patch := range imp.Patches { - err := validatePatch(patch) - if err != nil { - return fmt.Errorf("invalid imports[%d].patches[%d]: %w", idx, patchIdx, err) - } - } - - for patchIdx, patch := range imp.ReversePatches { - err := validatePatch(patch) - if err != nil { - return fmt.Errorf("invalid imports[%d].reversPatches[%d]: %w", idx, patchIdx, err) - } - } + // build config + retConfig := &VirtualClusterConfig{ + Config: *rawConfig, + Name: name, + ServiceName: name, + } + if name == "" { + return nil, fmt.Errorf("environment variable VCLUSTER_NAME is not defined") } - if config.Hooks != nil { - // HostToVirtual validation - for idx, hook := range config.Hooks.HostToVirtual { - for idy, verb := range hook.Verbs { - if err := validateVerb(verb); err != nil { - return fmt.Errorf("invalid hooks.hostToVirtual[%d].verbs[%d]: %w", idx, idy, err) - } - } - - for idy, patch := range hook.Patches { - if err := validatePatch(patch); err != nil { - return fmt.Errorf("invalid hooks.hostToVirtual[%d].patches[%d]: %w", idx, idy, err) - } - } - } - - // VirtualToHost validation - for idx, hook := range config.Hooks.VirtualToHost { - for idy, verb := range hook.Verbs { - if err := validateVerb(verb); err != nil { - return fmt.Errorf("invalid hooks.virtualToHost[%d].verbs[%d]: %w", idx, idy, err) - } - } - - for idy, patch := range hook.Patches { - if err := validatePatch(patch); err != nil { - return fmt.Errorf("invalid hooks.virtualToHost[%d].patches[%d]: %w", idx, idy, err) - } - } - } + // validate config + err = ValidateConfigAndSetDefaults(retConfig) + if err != nil { + return nil, err } - return nil + return retConfig, nil } -func validatePatch(patch *Patch) error { - switch patch.Operation { - case PatchTypeRemove, PatchTypeReplace, PatchTypeAdd: - if patch.FromPath != "" { - return fmt.Errorf("fromPath is not supported for this operation") - } - - return nil - case PatchTypeRewriteName, PatchTypeRewriteLabelSelector, PatchTypeRewriteLabelKey, PatchTypeRewriteLabelExpressionsSelector: - return nil - case PatchTypeCopyFromObject: - if patch.FromPath == "" { - return fmt.Errorf("fromPath is required for this operation") - } - - return nil - default: - return fmt.Errorf("unsupported patch type %s", patch.Operation) +func applySetValues(rawConfig []byte, setValues []string) ([]byte, error) { + if len(setValues) == 0 { + return rawConfig, nil } -} -func validateVerb(verb string) error { - if !lo.Contains(verbs, verb) { - return fmt.Errorf("invalid verb \"%s\"; expected on of %q", verb, verbs) + // parse raw config + rawConfigMap := map[string]interface{}{} + err := yaml.Unmarshal(rawConfig, &rawConfigMap) + if err != nil { + return nil, fmt.Errorf("parse raw config: %w", err) } - return nil -} - -func validateExportDuplicates(exports []*Export) error { - gvks := map[string]bool{} - for _, e := range exports { - k := fmt.Sprintf("%s|%s", e.APIVersion, e.Kind) - _, found := gvks[k] - if found { - return fmt.Errorf("duplicate export for APIVersion %s and %s Kind, only one export for each APIVersion+Kind is permitted", e.APIVersion, e.Kind) + // merge set + for _, set := range setValues { + err = strvals.ParseIntoString(set, rawConfigMap) + if err != nil { + return nil, fmt.Errorf("apply --set %s: %w", set, err) } - gvks[k] = true } - return nil -} - -func validateImportDuplicates(imports []*Import) error { - gvks := map[string]bool{} - for _, e := range imports { - k := fmt.Sprintf("%s|%s", e.APIVersion, e.Kind) - _, found := gvks[k] - if found { - return fmt.Errorf("duplicate import for APIVersion %s and %s Kind, only one import for each APIVersion+Kind is permitted", e.APIVersion, e.Kind) - } - gvks[k] = true + // marshal again + rawConfig, err = yaml.Marshal(rawConfigMap) + if err != nil { + return nil, fmt.Errorf("marshal config bytes: %w", err) } - return nil + return rawConfig, nil } diff --git a/pkg/config/validation.go b/pkg/config/validation.go new file mode 100644 index 000000000..2ee69f3f8 --- /dev/null +++ b/pkg/config/validation.go @@ -0,0 +1,435 @@ +package config + +import ( + "crypto/x509" + "errors" + "fmt" + "net/url" + "slices" + + "github.com/ghodss/yaml" + "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/util/toleration" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "k8s.io/apimachinery/pkg/api/validation" +) + +var allowedPodSecurityStandards = map[string]bool{ + "privileged": true, + "baseline": true, + "restricted": true, +} + +var ( + verbs = []string{"get", "list", "create", "update", "patch", "watch", "delete", "deletecollection"} +) + +func ValidateConfigAndSetDefaults(config *VirtualClusterConfig) error { + // check the value of pod security standard + if config.Policies.PodSecurityStandard != "" && !allowedPodSecurityStandards[config.Policies.PodSecurityStandard] { + return fmt.Errorf("invalid argument enforce-pod-security-standard=%s, must be one of: privileged, baseline, restricted", config.Policies.PodSecurityStandard) + } + + // parse tolerations + for _, t := range config.Sync.ToHost.Pods.EnforceTolerations { + _, err := toleration.ParseToleration(t) + if err != nil { + return err + } + } + + // check if enable scheduler works correctly + if config.ControlPlane.Advanced.VirtualScheduler.Enabled && !config.Sync.FromHost.Nodes.Selector.All && len(config.Sync.FromHost.Nodes.Selector.Labels) == 0 { + config.Sync.FromHost.Nodes.Selector.All = true + } + + // enable additional controllers required for scheduling with storage + if config.ControlPlane.Advanced.VirtualScheduler.Enabled && config.Sync.ToHost.PersistentVolumeClaims.Enabled { + config.Sync.FromHost.CSINodes.Enabled = true + config.Sync.FromHost.CSIStorageCapacities.Enabled = true + config.Sync.FromHost.CSIDrivers.Enabled = true + if !config.Sync.FromHost.StorageClasses.Enabled && !config.Sync.ToHost.StorageClasses.Enabled { + config.Sync.FromHost.StorageClasses.Enabled = true + } + } + + // check if nodes controller needs to be enabled + if config.ControlPlane.Advanced.VirtualScheduler.Enabled && !config.Sync.FromHost.Nodes.Enabled { + return fmt.Errorf("sync.fromHost.nodes.enabled is false, but required if using virtual scheduler") + } + + // check if storage classes and host storage classes are enabled at the same time + if config.Sync.FromHost.StorageClasses.Enabled && config.Sync.ToHost.StorageClasses.Enabled { + return fmt.Errorf("you cannot enable both sync.fromHost.storageClasses.enabled and sync.toHost.storageClasses.enabled at the same time. Choose only one of them") + } + + // validate central admission control + err := validateCentralAdmissionControl(config) + if err != nil { + return err + } + + // validate generic sync config + err = validateGenericSyncConfig(config.Experimental.GenericSync) + if err != nil { + return fmt.Errorf("validate experimental.genericSync") + } + + // validate distro + err = validateDistro(config) + if err != nil { + return err + } + + // check deny proxy requests + for _, c := range config.Experimental.DenyProxyRequests { + err := validateCheck(c) + if err != nil { + return err + } + } + + // check resolve dns + err = validateMappings(config.Networking.ResolveDNS) + if err != nil { + return err + } + + return nil +} + +func validateDistro(config *VirtualClusterConfig) error { + enabledDistros := 0 + if config.Config.ControlPlane.Distro.K3S.Enabled { + enabledDistros++ + } + if config.Config.ControlPlane.Distro.K0S.Enabled { + enabledDistros++ + } + if config.Config.ControlPlane.Distro.K8S.Enabled { + enabledDistros++ + } + if config.Config.ControlPlane.Distro.EKS.Enabled { + enabledDistros++ + } + + if enabledDistros > 1 { + return fmt.Errorf("only one distribution can be enabled") + } + return nil +} + +func validateGenericSyncConfig(config config.ExperimentalGenericSync) error { + err := validateExportDuplicates(config.Exports) + if err != nil { + return err + } + + for idx, exp := range config.Exports { + if exp == nil { + return fmt.Errorf("exports[%d] is required", idx) + } + + if exp.Kind == "" { + return fmt.Errorf("exports[%d].kind is required", idx) + } + + if exp.APIVersion == "" { + return fmt.Errorf("exports[%d].APIVersion is required", idx) + } + + for patchIdx, patch := range exp.Patches { + err := validatePatch(patch) + if err != nil { + return fmt.Errorf("invalid exports[%d].patches[%d]: %w", idx, patchIdx, err) + } + } + + for patchIdx, patch := range exp.ReversePatches { + err := validatePatch(patch) + if err != nil { + return fmt.Errorf("invalid exports[%d].reversPatches[%d]: %w", idx, patchIdx, err) + } + } + } + + err = validateImportDuplicates(config.Imports) + if err != nil { + return err + } + + for idx, imp := range config.Imports { + if imp == nil { + return fmt.Errorf("imports[%d] is required", idx) + } + + if imp.Kind == "" { + return fmt.Errorf("imports[%d].kind is required", idx) + } + + if imp.APIVersion == "" { + return fmt.Errorf("imports[%d].APIVersion is required", idx) + } + + for patchIdx, patch := range imp.Patches { + err := validatePatch(patch) + if err != nil { + return fmt.Errorf("invalid imports[%d].patches[%d]: %w", idx, patchIdx, err) + } + } + + for patchIdx, patch := range imp.ReversePatches { + err := validatePatch(patch) + if err != nil { + return fmt.Errorf("invalid imports[%d].reversPatches[%d]: %w", idx, patchIdx, err) + } + } + } + + if config.Hooks != nil { + // HostToVirtual validation + for idx, hook := range config.Hooks.HostToVirtual { + for idy, verb := range hook.Verbs { + if err := validateVerb(verb); err != nil { + return fmt.Errorf("invalid hooks.hostToVirtual[%d].verbs[%d]: %w", idx, idy, err) + } + } + + for idy, patch := range hook.Patches { + if err := validatePatch(patch); err != nil { + return fmt.Errorf("invalid hooks.hostToVirtual[%d].patches[%d]: %w", idx, idy, err) + } + } + } + + // VirtualToHost validation + for idx, hook := range config.Hooks.VirtualToHost { + for idy, verb := range hook.Verbs { + if err := validateVerb(verb); err != nil { + return fmt.Errorf("invalid hooks.virtualToHost[%d].verbs[%d]: %w", idx, idy, err) + } + } + + for idy, patch := range hook.Patches { + if err := validatePatch(patch); err != nil { + return fmt.Errorf("invalid hooks.virtualToHost[%d].patches[%d]: %w", idx, idy, err) + } + } + } + } + + return nil +} + +func validatePatch(patch *config.Patch) error { + switch patch.Operation { + case config.PatchTypeRemove, config.PatchTypeReplace, config.PatchTypeAdd: + if patch.FromPath != "" { + return fmt.Errorf("fromPath is not supported for this operation") + } + + return nil + case config.PatchTypeRewriteName, config.PatchTypeRewriteLabelSelector, config.PatchTypeRewriteLabelKey, config.PatchTypeRewriteLabelExpressionsSelector: + return nil + case config.PatchTypeCopyFromObject: + if patch.FromPath == "" { + return fmt.Errorf("fromPath is required for this operation") + } + + return nil + default: + return fmt.Errorf("unsupported patch type %s", patch.Operation) + } +} + +func validateVerb(verb string) error { + if !slices.Contains(verbs, verb) { + return fmt.Errorf("invalid verb \"%s\"; expected on of %q", verb, verbs) + } + + return nil +} + +func validateExportDuplicates(exports []*config.Export) error { + gvks := map[string]bool{} + for _, e := range exports { + k := fmt.Sprintf("%s|%s", e.APIVersion, e.Kind) + _, found := gvks[k] + if found { + return fmt.Errorf("duplicate export for APIVersion %s and %s Kind, only one export for each APIVersion+Kind is permitted", e.APIVersion, e.Kind) + } + gvks[k] = true + } + + return nil +} + +func validateImportDuplicates(imports []*config.Import) error { + gvks := map[string]bool{} + for _, e := range imports { + k := fmt.Sprintf("%s|%s", e.APIVersion, e.Kind) + _, found := gvks[k] + if found { + return fmt.Errorf("duplicate import for APIVersion %s and %s Kind, only one import for each APIVersion+Kind is permitted", e.APIVersion, e.Kind) + } + gvks[k] = true + } + + return nil +} + +func validateCentralAdmissionControl(config *VirtualClusterConfig) error { + _, _, err := ParseExtraHooks(config.Policies.CentralAdmission.ValidatingWebhooks, config.Policies.CentralAdmission.MutatingWebhooks) + return err +} + +func ParseExtraHooks(valHooks, mutHooks []interface{}) ([]admissionregistrationv1.ValidatingWebhookConfiguration, []admissionregistrationv1.MutatingWebhookConfiguration, error) { + decodedVal := make([]string, 0, len(valHooks)) + for _, v := range valHooks { + bytes, err := yaml.Marshal(v) + if err != nil { + return nil, nil, err + } + decodedVal = append(decodedVal, string(bytes)) + } + decodedMut := make([]string, 0, len(mutHooks)) + for _, v := range mutHooks { + bytes, err := yaml.Marshal(v) + if err != nil { + return nil, nil, err + } + decodedMut = append(decodedMut, string(bytes)) + } + + validateConfs := make([]admissionregistrationv1.ValidatingWebhookConfiguration, 0, len(valHooks)) + mutateConfs := make([]admissionregistrationv1.MutatingWebhookConfiguration, 0, len(mutHooks)) + for _, v := range decodedVal { + var valHook admissionregistrationv1.ValidatingWebhookConfiguration + err := yaml.Unmarshal([]byte(v), &valHook) + if err != nil { + return nil, nil, err + } + for _, v := range valHook.Webhooks { + err := validateWebhookClientCfg(v.ClientConfig) + if err != nil { + return nil, nil, fmt.Errorf("webhook client config was not valid for ValidatingWebhookConfiguration %s: %w", v.Name, err) + } + } + validateConfs = append(validateConfs, valHook) + } + for _, v := range decodedMut { + var mutHook admissionregistrationv1.MutatingWebhookConfiguration + err := yaml.Unmarshal([]byte(v), &mutHook) + if err != nil { + return nil, nil, err + } + for _, v := range mutHook.Webhooks { + err := validateWebhookClientCfg(v.ClientConfig) + if err != nil { + return nil, nil, fmt.Errorf("webhook client config was not valid for MutatingWebhookConfiguration %s: %w", v.Name, err) + } + } + mutateConfs = append(mutateConfs, mutHook) + } + + return validateConfs, mutateConfs, nil +} + +func validateWebhookClientCfg(clientCfg admissionregistrationv1.WebhookClientConfig) error { + if len(clientCfg.CABundle) != 0 { + ok := x509.NewCertPool().AppendCertsFromPEM(clientCfg.CABundle) + if !ok { + return errors.New("could not parse the CABundle") + } + } + + if clientCfg.Service == nil && clientCfg.URL == nil { + return errors.New("there is no service config") + } + + if clientCfg.Service != nil && (clientCfg.Service.Name == "" || clientCfg.Service.Namespace == "") { + return errors.New("namespace or name of the service is missing") + } + + if clientCfg.URL != nil { + _, err := url.Parse(*clientCfg.URL) + if err != nil { + return errors.New("the url was not valid") + } + } + + return nil +} + +func validateCheck(check config.DenyRule) error { + for _, ns := range check.Namespaces { + errors := validation.ValidateNamespaceName(ns, false) + if len(errors) != 0 { + return fmt.Errorf("invalid Namespaces in %q check: %v", check.Name, errors) + } + } + var err error + for _, r := range check.Rules { + err = validateWildcardOrExact(r.Verbs, "create", "get", "update", "patch", "delete") + if err != nil { + return fmt.Errorf("invalid Verb defined in the %q check: %w", check.Name, err) + } + + err = validateWildcardOrAny(r.APIGroups) + if err != nil { + return fmt.Errorf("invalid APIGroup defined in the %q check: %w", check.Name, err) + } + + err = validateWildcardOrAny(r.APIVersions) + if err != nil { + return fmt.Errorf("invalid APIVersion defined in the %q check: %w", check.Name, err) + } + + if r.Scope != nil { + switch *r.Scope { + case string(admissionregistrationv1.ClusterScope): + case string(admissionregistrationv1.NamespacedScope): + case string(admissionregistrationv1.AllScopes): + default: + return fmt.Errorf("invalid Scope defined in the %q check: %q", check.Name, *r.Scope) + } + } + } + return nil +} + +func validateWildcardOrExact(values []string, validValues ...string) error { + if len(values) == 1 && values[0] == "*" { + return nil + } + for _, val := range values { + if val == "*" { + return fmt.Errorf("when wildcard(*) is used, it must be the only value in the list") + } + + // empty list of validValues means any value is valid + valid := len(validValues) == 0 + for _, v := range validValues { + if val == v { + valid = true + break + } + } + if !valid { + return fmt.Errorf("invalid value %q", val) + } + } + return nil +} + +func validateWildcardOrAny(values []string) error { + if len(values) == 1 && values[0] == "*" { + return nil + } + for _, val := range values { + if val == "*" { + return fmt.Errorf("when wildcard(*) is used, it must be the only value in the list") + } + } + return nil +} diff --git a/pkg/pro/validation_test.go b/pkg/config/validation_test.go similarity index 70% rename from pkg/pro/validation_test.go rename to pkg/config/validation_test.go index 86f584647..9797ed758 100644 --- a/pkg/pro/validation_test.go +++ b/pkg/config/validation_test.go @@ -1,10 +1,8 @@ -package pro +package config import ( - "encoding/base64" "testing" - "github.com/ghodss/yaml" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" ) @@ -52,54 +50,44 @@ HYMfRsCbvUOZ58SWLs5fyQ== testCases := []struct { name string wantErr string - valHook []string - mutHook []string + valHook []interface{} + mutHook []interface{} }{ - { - name: "not valid base64 validating", - valHook: []string{"hello there"}, - wantErr: "illegal base64 data at input byte 5", - }, - { - name: "not valid base64 mutating", - valHook: []string{"hello there"}, - wantErr: "illegal base64 data at input byte 5", - }, { name: "valid valhook", - valHook: []string{valHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, + valHook: []interface{}{valHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, }, { name: "valid muthook", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "service"}})}, }, { name: "invalid valhook", - valHook: []string{valHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, + valHook: []interface{}{valHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, wantErr: "webhook client config was not valid for ValidatingWebhookConfiguration test: there is no service config", }, { name: "invalid muthook", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: there is no service config", }, { name: "invalid service", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: namespace or name of the service is missing", }, { name: "valid url", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{URL: &validURL})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{URL: &validURL})}, }, { name: "invalid bundle", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}, CABundle: []byte("HAZAA")})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test"}, CABundle: []byte("HAZAA")})}, wantErr: "webhook client config was not valid for MutatingWebhookConfiguration test: could not parse the CABundle", }, { name: "valid bundle", - mutHook: []string{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "test"}, CABundle: []byte(validCABUNDLE)})}, + mutHook: []interface{}{mutHookToBase64(admissionregistrationv1.WebhookClientConfig{Service: &admissionregistrationv1.ServiceReference{Namespace: "test", Name: "test"}, CABundle: []byte(validCABUNDLE)})}, }, } for _, tt := range testCases { @@ -114,32 +102,24 @@ HYMfRsCbvUOZ58SWLs5fyQ== } } -func valHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) string { - hook := admissionregistrationv1.ValidatingWebhookConfiguration{} +func valHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) interface{} { + hook := &admissionregistrationv1.ValidatingWebhookConfiguration{} hook.APIVersion = "v1" hook.Kind = "ValidatingWebhookConfiguration" hook.Name = "test" hook.Webhooks = []admissionregistrationv1.ValidatingWebhook{ {Name: "test", ClientConfig: clientCfg}, } - bytes, err := yaml.Marshal(hook) - if err != nil { - return "" - } - return base64.StdEncoding.EncodeToString(bytes) + return hook } -func mutHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) string { - hook := admissionregistrationv1.MutatingWebhookConfiguration{} +func mutHookToBase64(clientCfg admissionregistrationv1.WebhookClientConfig) interface{} { + hook := &admissionregistrationv1.MutatingWebhookConfiguration{} hook.APIVersion = "v1" hook.Kind = "MutatingWebhookConfiguration" hook.Name = "test" hook.Webhooks = []admissionregistrationv1.MutatingWebhook{ {Name: "test", ClientConfig: clientCfg}, } - bytes, err := yaml.Marshal(hook) - if err != nil { - return "" - } - return base64.StdEncoding.EncodeToString(bytes) + return hook } diff --git a/pkg/constants/controllers.go b/pkg/constants/controllers.go deleted file mode 100644 index 9e4c20536..000000000 --- a/pkg/constants/controllers.go +++ /dev/null @@ -1,50 +0,0 @@ -package constants - -import "k8s.io/apimachinery/pkg/util/sets" - -var ExistingControllers = sets.New( - "services", - "configmaps", - "secrets", - "endpoints", - "pods", - "events", - "fake-nodes", - "fake-persistentvolumes", - "persistentvolumeclaims", - "ingresses", - "ingressclasses", - "nodes", - "persistentvolumes", - "storageclasses", - "hoststorageclasses", - "priorityclasses", - "networkpolicies", - "volumesnapshots", - "poddisruptionbudgets", - "serviceaccounts", - "csinodes", - "csidrivers", - "csistoragecapacities", - "namespaces", -) - -var DefaultEnabledControllers = sets.New( - // helm charts need to be updated when changing this! - // values.yaml and template/_helpers.tpl reference these - "services", - "configmaps", - "secrets", - "endpoints", - "pods", - "events", - "persistentvolumeclaims", - "fake-nodes", - "fake-persistentvolumes", -) - -var SchedulerRequiredControllers = sets.New( - "csinodes", - "csidrivers", - "csistoragecapacities", -) diff --git a/pkg/constants/distro.go b/pkg/constants/distro.go deleted file mode 100644 index 824b616d4..000000000 --- a/pkg/constants/distro.go +++ /dev/null @@ -1,21 +0,0 @@ -package constants - -import "os" - -const ( - K3SDistro = "k3s" - K8SDistro = "k8s" - K0SDistro = "k0s" - EKSDistro = "eks" - Unknown = "unknown" -) - -func GetVClusterDistro() string { - distro := os.Getenv("VCLUSTER_DISTRO") - switch distro { - case K3SDistro, K8SDistro, K0SDistro, EKSDistro: - return distro - default: - return Unknown - } -} diff --git a/pkg/controllers/manifests/init.go b/pkg/controllers/deploy/deploy.go similarity index 70% rename from pkg/controllers/manifests/init.go rename to pkg/controllers/deploy/deploy.go index 42834cce2..f9a9432ba 100644 --- a/pkg/controllers/manifests/init.go +++ b/pkg/controllers/deploy/deploy.go @@ -1,4 +1,4 @@ -package manifests +package deploy import ( "context" @@ -13,15 +13,18 @@ import ( "strings" "time" + vclusterconfig "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/config" + "github.com/loft-sh/vcluster/pkg/k0s" "github.com/pkg/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" "github.com/ghodss/yaml" "github.com/loft-sh/vcluster/pkg/helm" "github.com/loft-sh/vcluster/pkg/util/compress" - "github.com/loft-sh/vcluster/pkg/util/translate" - "github.com/loft-sh/vcluster/pkg/util/loghelper" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" @@ -31,14 +34,11 @@ import ( type InitObjectStatus string const ( - InitChartsKey = "charts" - InitManifestsKey = "manifests" - InitManifestSuffix = "-init-manifests" - StatusFailed InitObjectStatus = "Failed" StatusSuccess InitObjectStatus = "Success" StatusPending InitObjectStatus = "Pending" - StatusKey = "vcluster.loft.sh/status" + + StatusKey = "vcluster.loft.sh/status" DefaultTimeOut = 180 * time.Second HelmWorkDir = "/tmp" @@ -47,45 +47,52 @@ const ( InstallError = "InstallFailed" UpgradeError = "UpgradeFailed" UninstallError = "UninstallFailed" + + VClusterDeployConfigMap = "vcluster-deploy" + VClusterDeployConfigMapNamespace = "kube-system" ) -type InitManifestsConfigMapReconciler struct { +type Deployer struct { Log loghelper.Logger - LocalClient client.Client VirtualManager ctrl.Manager - - HelmClient helm.Client + HelmClient helm.Client } -func (r *InitManifestsConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { - // TODO: implement better filtration through predicates - if req.Name != translate.VClusterName+InitManifestSuffix { - return ctrl.Result{}, nil - } - +func (r *Deployer) Apply(ctx context.Context, vConfig *config.VirtualClusterConfig) (result ctrl.Result, err error) { // get config map - cm := &corev1.ConfigMap{} - err = r.LocalClient.Get(ctx, req.NamespacedName, cm) - if err != nil { - if kerrors.IsNotFound(err) { + configMap := &corev1.ConfigMap{} + err = r.VirtualManager.GetClient().Get(ctx, types.NamespacedName{Name: VClusterDeployConfigMap, Namespace: VClusterDeployConfigMapNamespace}, configMap) + if kerrors.IsNotFound(err) { + if vConfig.Experimental.Deploy.Manifests == "" && vConfig.Experimental.Deploy.ManifestsTemplate == "" && len(vConfig.Experimental.Deploy.Helm) == 0 { return ctrl.Result{}, nil } + configMap = &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: VClusterDeployConfigMap, + Namespace: VClusterDeployConfigMapNamespace, + }, + } + err = r.VirtualManager.GetClient().Create(ctx, configMap) + if err != nil { + return ctrl.Result{}, fmt.Errorf("create deploy status config map: %w", err) + } + } else if err != nil { return ctrl.Result{}, err } // patch the status to the configmap - oldConfigMap := cm.DeepCopy() + oldConfigMap := configMap.DeepCopy() defer func() { - patchErr := r.UpdateConfigMap(ctx, err, result.Requeue, oldConfigMap, cm) + patchErr := r.UpdateConfigMap(ctx, err, result.Requeue, oldConfigMap, configMap) if patchErr != nil && err == nil { err = patchErr } }() // process the init manifests - requeue, err := r.ProcessInitManifests(ctx, cm) + requeue, err := r.ProcessInitManifests(ctx, vConfig, configMap) if err != nil { return ctrl.Result{}, err } else if requeue { @@ -93,7 +100,7 @@ func (r *InitManifestsConfigMapReconciler) Reconcile(ctx context.Context, req ct } // process the helm charts - requeue, err = r.ProcessHelmChart(ctx, cm) + requeue, err = r.ProcessHelmChart(ctx, vConfig, configMap) if err != nil { return ctrl.Result{}, err } else if requeue { @@ -104,7 +111,7 @@ func (r *InitManifestsConfigMapReconciler) Reconcile(ctx context.Context, req ct return ctrl.Result{}, nil } -func (r *InitManifestsConfigMapReconciler) UpdateConfigMap(ctx context.Context, lastError error, requeue bool, oldConfigMap *corev1.ConfigMap, newConfigMap *corev1.ConfigMap) error { +func (r *Deployer) UpdateConfigMap(ctx context.Context, lastError error, requeue bool, oldConfigMap *corev1.ConfigMap, newConfigMap *corev1.ConfigMap) error { currentStatus := ParseStatus(newConfigMap) // set phase initially to pending @@ -161,7 +168,7 @@ func (r *InitManifestsConfigMapReconciler) UpdateConfigMap(ctx context.Context, // try patching the configmap r.Log.Debugf("Patch init config map with: %s", string(rawPatch)) - err = r.LocalClient.Patch(ctx, newConfigMap, client.RawPatch(patch.Type(), rawPatch)) + err = r.VirtualManager.GetClient().Patch(ctx, newConfigMap, client.RawPatch(patch.Type(), rawPatch)) if err != nil { r.Log.Errorf("error updating configmap status: %v", err) return err @@ -170,12 +177,20 @@ func (r *InitManifestsConfigMapReconciler) UpdateConfigMap(ctx context.Context, return nil } -func (r *InitManifestsConfigMapReconciler) ProcessInitManifests(ctx context.Context, cm *corev1.ConfigMap) (bool, error) { +func (r *Deployer) ProcessInitManifests(ctx context.Context, vConfig *config.VirtualClusterConfig, configMap *corev1.ConfigMap) (bool, error) { var err error - manifests := cm.Data[InitManifestsKey] + manifests := vConfig.Experimental.Deploy.Manifests + if vConfig.Experimental.Deploy.ManifestsTemplate != "" { + templatedManifests, err := k0s.ExecTemplate(vConfig.Experimental.Deploy.ManifestsTemplate, vConfig.Name, vConfig.TargetNamespace, &vConfig.Config) + if err != nil { + return false, fmt.Errorf("exec manifests template: %w", err) + } + + manifests += "\n---\n" + string(templatedManifests) + } // make array stable or otherwise order is random - status := ParseStatus(cm) + status := ParseStatus(configMap) // get last applied manifests lastAppliedManifests := "" @@ -189,14 +204,14 @@ func (r *InitManifestsConfigMapReconciler) ProcessInitManifests(ctx context.Cont // should skip? if manifests == lastAppliedManifests { - return false, r.setManifestsStatus(cm, StatusSuccess, "", "") + return false, r.setManifestsStatus(configMap, StatusSuccess, "", "") } // apply manifests err = ApplyGivenInitManifests(ctx, r.VirtualManager.GetClient(), r.VirtualManager.GetConfig(), manifests, lastAppliedManifests) if err != nil { r.Log.Errorf("error applying init manifests: %v", err) - _ = r.setManifestsStatus(cm, StatusFailed, InstallError, err.Error()) + _ = r.setManifestsStatus(configMap, StatusFailed, InstallError, err.Error()) return false, err } @@ -209,27 +224,20 @@ func (r *InitManifestsConfigMapReconciler) ProcessInitManifests(ctx context.Cont // update annotation status.Manifests.LastAppliedManifests = compressedManifests - err = r.encodeStatus(cm, status) + err = r.encodeStatus(configMap, status) if err != nil { return false, err } - return true, r.setManifestsStatus(cm, StatusSuccess, "", "") + return true, r.setManifestsStatus(configMap, StatusSuccess, "", "") } -func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, cm *corev1.ConfigMap) (bool, error) { - statusMap, err := r.getStatusMap(cm) +func (r *Deployer) ProcessHelmChart(ctx context.Context, vConfig *config.VirtualClusterConfig, configMap *corev1.ConfigMap) (bool, error) { + statusMap, err := r.getStatusMap(configMap) if err != nil { return false, err } - var charts []Chart - if cm.Data[InitChartsKey] != "" { - err = yaml.Unmarshal([]byte(cm.Data[InitChartsKey]), &charts) - if err != nil { - return false, err - } - } - + charts := vConfig.Experimental.Deploy.Helm for _, chart := range charts { releaseName, releaseNamespace := r.getTargetRelease(chart) r.Log.Debugf("processing helm chart for %s/%s", releaseNamespace, releaseName) @@ -237,7 +245,7 @@ func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, err := r.pullChartArchive(ctx, chart) if err != nil { - _ = r.setChartStatus(cm, &chart, StatusFailed, ChartPullError, err.Error()) + _ = r.setChartStatus(configMap, &chart, StatusFailed, ChartPullError, err.Error()) return false, err } @@ -249,19 +257,19 @@ func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, r.Log.Debugf("release %s/%s already exists", releaseNamespace, releaseName) // check if upgrade is needed - upgradedNeeded, err := r.checkIfUpgradeNeeded(cm, chart) + upgradedNeeded, err := r.checkIfUpgradeNeeded(configMap, chart) if err != nil { return false, err } else if upgradedNeeded { // initiate upgrade err = r.initiateUpgrade(ctx, chart) if err != nil { - _ = r.setChartStatus(cm, &chart, StatusFailed, UpgradeError, err.Error()) + _ = r.setChartStatus(configMap, &chart, StatusFailed, UpgradeError, err.Error()) return false, err } // update last applied chart config - err = r.setChartStatusLastApplied(cm, &chart) + err = r.setChartStatusLastApplied(configMap, &chart) if err != nil { r.Log.Errorf("error updating config map with last applied chart annotation: %v", err) return false, err @@ -279,12 +287,12 @@ func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, err = r.initiateInstall(ctx, chart) if err != nil { r.Log.Errorf("error installing release %s/%s", releaseNamespace, releaseName) - _ = r.setChartStatus(cm, &chart, StatusFailed, InstallError, err.Error()) + _ = r.setChartStatus(configMap, &chart, StatusFailed, InstallError, err.Error()) return false, err } // update last applied chart config - err = r.setChartStatusLastApplied(cm, &chart) + err = r.setChartStatusLastApplied(configMap, &chart) if err != nil { r.Log.Errorf("error updating config map with last applied chart annotation: %v", err) return false, err @@ -298,7 +306,7 @@ func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, if len(statusMap) > 0 { r.Log.Debugf("following charts left in status map, should be deleted: %v", statusMap) for _, chartStatus := range statusMap { - err := r.deleteHelmRelease(cm, chartStatus) + err := r.deleteHelmRelease(configMap, chartStatus) if err != nil { return false, errors.Wrap(err, "delete helm release") } @@ -308,7 +316,7 @@ func (r *InitManifestsConfigMapReconciler) ProcessHelmChart(ctx context.Context, return false, nil } -func (r *InitManifestsConfigMapReconciler) checkIfUpgradeNeeded(cm *corev1.ConfigMap, chart Chart) (bool, error) { +func (r *Deployer) checkIfUpgradeNeeded(cm *corev1.ConfigMap, chart vclusterconfig.ExperimentalDeployHelm) (bool, error) { name, namespace := r.getTargetRelease(chart) // find chart status @@ -327,7 +335,7 @@ func (r *InitManifestsConfigMapReconciler) checkIfUpgradeNeeded(cm *corev1.Confi return true, nil } -func (r *InitManifestsConfigMapReconciler) initiateUpgrade(ctx context.Context, chart Chart) error { +func (r *Deployer) initiateUpgrade(ctx context.Context, chart vclusterconfig.ExperimentalDeployHelm) error { name, namespace := r.getTargetRelease(chart) path, err := r.findChart(chart) if err != nil { @@ -383,14 +391,14 @@ func getTarballPath(helmWorkDir, repo, name, version string) (tarballPath, tarba return tarballPath, tarballDir } -func (r *InitManifestsConfigMapReconciler) findChart(chart Chart) (string, error) { - tarballPath, tarballDir := getTarballPath(HelmWorkDir, chart.Repo, chart.Name, chart.Version) +func (r *Deployer) findChart(chart vclusterconfig.ExperimentalDeployHelm) (string, error) { + tarballPath, tarballDir := getTarballPath(HelmWorkDir, chart.Chart.Repo, chart.Chart.Name, chart.Chart.Version) // if version specified, look for specific file - if chart.Version != "" { + if chart.Chart.Version != "" { pathsToTry := []string{tarballPath} // try with alternate names as well - if chart.Version[0] != 'v' { - tarballPathWithV, _ := getTarballPath(HelmWorkDir, chart.Repo, chart.Name, fmt.Sprintf("v%s", chart.Version)) + if chart.Chart.Version[0] != 'v' { + tarballPathWithV, _ := getTarballPath(HelmWorkDir, chart.Chart.Repo, chart.Chart.Name, fmt.Sprintf("v%s", chart.Chart.Version)) pathsToTry = append(pathsToTry, tarballPathWithV) } for _, path := range pathsToTry { @@ -410,7 +418,7 @@ func (r *InitManifestsConfigMapReconciler) findChart(chart Chart) (string, error return "", err } for _, f := range files { - if strings.HasPrefix(f.Name(), chart.Name+"-") { + if strings.HasPrefix(f.Name(), chart.Chart.Name+"-") { return filepath.Join(tarballDir, f.Name()), nil } } @@ -419,7 +427,7 @@ func (r *InitManifestsConfigMapReconciler) findChart(chart Chart) (string, error return "", nil } -func (r *InitManifestsConfigMapReconciler) initiateInstall(ctx context.Context, chart Chart) error { +func (r *Deployer) initiateInstall(ctx context.Context, chart vclusterconfig.ExperimentalDeployHelm) error { // initiate install name, namespace := r.getTargetRelease(chart) path, err := r.findChart(chart) @@ -461,7 +469,7 @@ func (r *InitManifestsConfigMapReconciler) initiateInstall(ctx context.Context, return nil } -func (r *InitManifestsConfigMapReconciler) getHashedConfig(chart Chart) (string, error) { +func (r *Deployer) getHashedConfig(chart vclusterconfig.ExperimentalDeployHelm) (string, error) { rawData, err := json.Marshal(chart) if err != nil { return "", err @@ -470,7 +478,7 @@ func (r *InitManifestsConfigMapReconciler) getHashedConfig(chart Chart) (string, return fmt.Sprintf("%x", md5.Sum(rawData)), nil } -func (r *InitManifestsConfigMapReconciler) releaseExists(chart Chart) (bool, error) { +func (r *Deployer) releaseExists(chart vclusterconfig.ExperimentalDeployHelm) (bool, error) { name, namespace := r.getTargetRelease(chart) ok, err := r.HelmClient.Exists(name, namespace) if err != nil { @@ -484,7 +492,7 @@ func (r *InitManifestsConfigMapReconciler) releaseExists(chart Chart) (bool, err return false, nil } -func (r *InitManifestsConfigMapReconciler) pullChartArchive(ctx context.Context, chart Chart) error { +func (r *Deployer) pullChartArchive(ctx context.Context, chart vclusterconfig.ExperimentalDeployHelm) error { tarballPath, err := r.findChart(chart) if err != nil { return err @@ -492,7 +500,7 @@ func (r *InitManifestsConfigMapReconciler) pullChartArchive(ctx context.Context, // check if tarball exists if tarballPath == "" { - tarballPath, tarballDir := getTarballPath(HelmWorkDir, chart.Repo, chart.Name, chart.Version) + tarballPath, tarballDir := getTarballPath(HelmWorkDir, chart.Chart.Repo, chart.Chart.Name, chart.Chart.Version) err := os.MkdirAll(tarballDir, 0755) if err != nil { return err @@ -508,29 +516,29 @@ func (r *InitManifestsConfigMapReconciler) pullChartArchive(ctx context.Context, return errors.Wrap(err, "write bundle to file") } } else { - helmErr := r.HelmClient.Pull(ctx, chart.Name, helm.UpgradeOptions{ - Chart: chart.Name, - Repo: chart.Repo, - Insecure: chart.Insecure, - Version: chart.Version, - Username: chart.Username, - Password: chart.Password, + helmErr := r.HelmClient.Pull(ctx, chart.Chart.Name, helm.UpgradeOptions{ + Chart: chart.Chart.Name, + Repo: chart.Chart.Repo, + Insecure: chart.Chart.Insecure, + Version: chart.Chart.Version, + Username: chart.Chart.Username, + Password: chart.Chart.Password, WorkDir: tarballDir, }) if helmErr != nil { - r.Log.Errorf("unable to pull chart %s: %v", chart.Name, helmErr) + r.Log.Errorf("unable to pull chart %s: %v", chart.Chart.Name, helmErr) return helmErr } - r.Log.Debugf("successfully pulled chart %s", chart.Name) + r.Log.Debugf("successfully pulled chart %s", chart.Chart.Name) } } return nil } -func (r *InitManifestsConfigMapReconciler) parseTimeout(chart Chart) time.Duration { +func (r *Deployer) parseTimeout(chart vclusterconfig.ExperimentalDeployHelm) time.Duration { t := chart.Timeout timeout, err := time.ParseDuration(t) if err != nil { @@ -542,7 +550,7 @@ func (r *InitManifestsConfigMapReconciler) parseTimeout(chart Chart) time.Durati return timeout } -func (r *InitManifestsConfigMapReconciler) rollbackOrUninstall(ctx context.Context, chartName, namespace string) error { +func (r *Deployer) rollbackOrUninstall(ctx context.Context, chartName, namespace string) error { output, err := r.HelmClient.Status(ctx, chartName, namespace) if err != nil { r.Log.Errorf("error getting helm status: %v", err) @@ -570,20 +578,20 @@ func (r *InitManifestsConfigMapReconciler) rollbackOrUninstall(ctx context.Conte return nil } -func (r *InitManifestsConfigMapReconciler) getTargetRelease(chart Chart) (string, string) { - name := chart.Name +func (r *Deployer) getTargetRelease(chart vclusterconfig.ExperimentalDeployHelm) (string, string) { + name := chart.Chart.Name namespace := corev1.NamespaceDefault - if chart.ReleaseName != "" { - name = chart.ReleaseName + if chart.Release.Name != "" { + name = chart.Release.Name } - if chart.ReleaseNamespace != "" { - namespace = chart.ReleaseNamespace + if chart.Release.Namespace != "" { + namespace = chart.Release.Namespace } return name, namespace } -func (r *InitManifestsConfigMapReconciler) getStatusMap(cm *corev1.ConfigMap) (map[string]ChartStatus, error) { +func (r *Deployer) getStatusMap(cm *corev1.ConfigMap) (map[string]ChartStatus, error) { statusMap := make(map[string]ChartStatus) status := ParseStatus(cm) for _, status := range status.Charts { @@ -593,7 +601,7 @@ func (r *InitManifestsConfigMapReconciler) getStatusMap(cm *corev1.ConfigMap) (m return statusMap, nil } -func (r *InitManifestsConfigMapReconciler) encodeStatus(cm *corev1.ConfigMap, status *Status) error { +func (r *Deployer) encodeStatus(cm *corev1.ConfigMap, status *Status) error { if cm.Annotations == nil { cm.Annotations = map[string]string{} } @@ -620,7 +628,7 @@ func ParseStatus(cm *corev1.ConfigMap) *Status { return status } -func (r *InitManifestsConfigMapReconciler) setManifestsStatus(cm *corev1.ConfigMap, phase InitObjectStatus, reason string, message string) error { +func (r *Deployer) setManifestsStatus(cm *corev1.ConfigMap, phase InitObjectStatus, reason string, message string) error { status := ParseStatus(cm) status.Manifests.Phase = string(phase) status.Manifests.Reason = reason @@ -628,7 +636,7 @@ func (r *InitManifestsConfigMapReconciler) setManifestsStatus(cm *corev1.ConfigM return r.encodeStatus(cm, status) } -func (r *InitManifestsConfigMapReconciler) setChartStatusLastApplied(cm *corev1.ConfigMap, chart *Chart) error { +func (r *Deployer) setChartStatusLastApplied(cm *corev1.ConfigMap, chart *vclusterconfig.ExperimentalDeployHelm) error { status := ParseStatus(cm) // get release name & namespace @@ -661,7 +669,7 @@ func (r *InitManifestsConfigMapReconciler) setChartStatusLastApplied(cm *corev1. return r.encodeStatus(cm, status) } -func (r *InitManifestsConfigMapReconciler) setChartStatus(cm *corev1.ConfigMap, chart *Chart, phase InitObjectStatus, reason string, message string) error { +func (r *Deployer) setChartStatus(cm *corev1.ConfigMap, chart *vclusterconfig.ExperimentalDeployHelm, phase InitObjectStatus, reason string, message string) error { status := ParseStatus(cm) // get release name & namespace @@ -691,7 +699,7 @@ func (r *InitManifestsConfigMapReconciler) setChartStatus(cm *corev1.ConfigMap, return r.encodeStatus(cm, status) } -func (r *InitManifestsConfigMapReconciler) popFromStatus(cm *corev1.ConfigMap, chartStatus ChartStatus) error { +func (r *Deployer) popFromStatus(cm *corev1.ConfigMap, chartStatus ChartStatus) error { status := ParseStatus(cm) found := -1 @@ -706,13 +714,17 @@ func (r *InitManifestsConfigMapReconciler) popFromStatus(cm *corev1.ConfigMap, c return r.encodeStatus(cm, status) } -func (r *InitManifestsConfigMapReconciler) deleteHelmRelease(cm *corev1.ConfigMap, chartStatus ChartStatus) error { +func (r *Deployer) deleteHelmRelease(cm *corev1.ConfigMap, chartStatus ChartStatus) error { err := r.HelmClient.Delete(chartStatus.Name, chartStatus.Namespace) if err != nil { r.Log.Infof("error deleting helm release %s/%s: %v", chartStatus.Namespace, chartStatus.Name, err) - return r.setChartStatus(cm, &Chart{ - Name: chartStatus.Name, - ReleaseNamespace: chartStatus.Namespace, + return r.setChartStatus(cm, &vclusterconfig.ExperimentalDeployHelm{ + Chart: vclusterconfig.ExperimentalDeployHelmChart{ + Name: chartStatus.Name, + }, + Release: vclusterconfig.ExperimentalDeployHelmRelease{ + Namespace: chartStatus.Namespace, + }, }, StatusFailed, UninstallError, err.Error()) } diff --git a/pkg/controllers/manifests/init_test.go b/pkg/controllers/deploy/deploy_test.go similarity index 98% rename from pkg/controllers/manifests/init_test.go rename to pkg/controllers/deploy/deploy_test.go index 81685b5f6..19832dccc 100644 --- a/pkg/controllers/manifests/init_test.go +++ b/pkg/controllers/deploy/deploy_test.go @@ -1,4 +1,4 @@ -package manifests +package deploy import ( "fmt" diff --git a/pkg/controllers/manifests/helper.go b/pkg/controllers/deploy/helper.go similarity index 99% rename from pkg/controllers/manifests/helper.go rename to pkg/controllers/deploy/helper.go index 9d2a2dd71..e2c0e3f89 100644 --- a/pkg/controllers/manifests/helper.go +++ b/pkg/controllers/deploy/helper.go @@ -1,4 +1,4 @@ -package manifests +package deploy import ( "context" @@ -26,7 +26,6 @@ func ApplyGivenInitManifests(ctx context.Context, vClient client.Client, vConfig klog.Errorf("unable to parse objects: %v", err) return errors.Wrap(err, "unable to parse objects") } - if len(lastAppliedObjects) == 0 && len(objs) == 0 { return nil } diff --git a/pkg/controllers/deploy/start.go b/pkg/controllers/deploy/start.go new file mode 100644 index 000000000..53f03f3ce --- /dev/null +++ b/pkg/controllers/deploy/start.go @@ -0,0 +1,55 @@ +package deploy + +import ( + "context" + "time" + + "github.com/loft-sh/log" + "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd" + "github.com/loft-sh/vcluster/pkg/config" + "github.com/loft-sh/vcluster/pkg/helm" + "github.com/loft-sh/vcluster/pkg/util/kubeconfig" + "github.com/loft-sh/vcluster/pkg/util/loghelper" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" +) + +func RegisterInitManifestsController(controllerCtx *config.ControllerContext) error { + vConfig, err := kubeconfig.ConvertRestConfigToClientConfig(controllerCtx.VirtualManager.GetConfig()) + if err != nil { + return err + } + + vConfigRaw, err := vConfig.RawConfig() + if err != nil { + return err + } + + helmBinaryPath, err := cmd.GetHelmBinaryPath(controllerCtx.Context, log.GetInstance()) + if err != nil { + return err + } + + controller := &Deployer{ + Log: loghelper.New("init-manifests-controller"), + VirtualManager: controllerCtx.VirtualManager, + + HelmClient: helm.NewClient(&vConfigRaw, log.GetInstance(), helmBinaryPath), + } + + go func() { + wait.JitterUntilWithContext(controllerCtx.Context, func(ctx context.Context) { + for { + result, err := controller.Apply(ctx, controllerCtx.Config) + if err != nil { + klog.Errorf("Error reconciling init_configmap: %v", err) + break + } else if !result.Requeue { + break + } + } + }, time.Second*10, 1.0, true) + }() + + return nil +} diff --git a/pkg/controllers/manifests/types.go b/pkg/controllers/deploy/types.go similarity index 58% rename from pkg/controllers/manifests/types.go rename to pkg/controllers/deploy/types.go index 0a21ed6d0..fea3fcd58 100644 --- a/pkg/controllers/manifests/types.go +++ b/pkg/controllers/deploy/types.go @@ -1,4 +1,4 @@ -package manifests +package deploy type Status struct { Phase string `json:"phase,omitempty"` @@ -9,8 +9,6 @@ type Status struct { Manifests ManifestsStatus `json:"manifests,omitempty"` } -//revive:disable - type ManifestsStatus struct { Phase string `json:"phase,omitempty"` Reason string `json:"reason,omitempty"` @@ -18,8 +16,6 @@ type ManifestsStatus struct { LastAppliedManifests string `json:"lastAppliedManifests,omitempty"` } -//revive:enable - type ChartStatus struct { Name string `json:"name,omitempty"` Namespace string `json:"namespace,omitempty"` @@ -28,17 +24,3 @@ type ChartStatus struct { Message string `json:"message,omitempty"` LastAppliedChartConfigHash string `json:"lastAppliedChartConfigHash,omitempty"` } - -type Chart struct { - Name string `json:"name,omitempty"` - Repo string `json:"repo,omitempty"` - Insecure bool `json:"insecure,omitempty"` - Version string `json:"version,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Values string `json:"values,omitempty"` - Timeout string `json:"timeout,omitempty"` - Bundle string `json:"bundle,omitempty"` - ReleaseName string `json:"releaseName,omitempty"` - ReleaseNamespace string `json:"releaseNamespace,omitempty"` -} diff --git a/pkg/controllers/manifests/unstructured.go b/pkg/controllers/deploy/unstructured.go similarity index 98% rename from pkg/controllers/manifests/unstructured.go rename to pkg/controllers/deploy/unstructured.go index 310c31cc2..0258bce76 100644 --- a/pkg/controllers/manifests/unstructured.go +++ b/pkg/controllers/deploy/unstructured.go @@ -1,4 +1,4 @@ -package manifests +package deploy import ( "fmt" diff --git a/pkg/controllers/generic/export_syncer.go b/pkg/controllers/generic/export_syncer.go index 1e0050fbb..d7cc17ba4 100644 --- a/pkg/controllers/generic/export_syncer.go +++ b/pkg/controllers/generic/export_syncer.go @@ -7,13 +7,13 @@ import ( "strings" "time" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" "github.com/loft-sh/vcluster/pkg/log" - "github.com/loft-sh/vcluster/pkg/config" + vclusterconfig "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/controllers/syncer" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" "github.com/loft-sh/vcluster/pkg/controllers/syncer/translator" @@ -32,7 +32,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func CreateExporters(ctx *options.ControllerContext, exporterConfig *config.Config) error { +func CreateExporters(ctx *config.ControllerContext) error { + exporterConfig := ctx.Config.Experimental.GenericSync if len(exporterConfig.Exports) == 0 { return nil } @@ -58,9 +59,9 @@ func CreateExporters(ctx *options.ControllerContext, exporterConfig *config.Conf } } - reversePatches := []*config.Patch{ + reversePatches := []*vclusterconfig.Patch{ { - Operation: config.PatchTypeCopyFromObject, + Operation: vclusterconfig.PatchTypeCopyFromObject, FromPath: "status", Path: "status", }, @@ -84,7 +85,7 @@ func CreateExporters(ctx *options.ControllerContext, exporterConfig *config.Conf return nil } -func createExporter(ctx *synccontext.RegisterContext, config *config.Export) (syncertypes.Syncer, error) { +func createExporter(ctx *synccontext.RegisterContext, config *vclusterconfig.Export) (syncertypes.Syncer, error) { obj := &unstructured.Unstructured{} obj.SetKind(config.Kind) obj.SetAPIVersion(config.APIVersion) @@ -127,7 +128,7 @@ type exporter struct { patcher *patcher gvk schema.GroupVersionKind - config *config.Export + config *vclusterconfig.Export selector labels.Selector name string } @@ -346,7 +347,7 @@ func (r *virtualToHostNameResolver) TranslateNamespaceRef(namespace string) (str return translate.Default.PhysicalNamespace(namespace), nil } -func validateExportConfig(config *config.Export) error { +func validateExportConfig(config *vclusterconfig.Export) error { for _, p := range append(config.Patches, config.ReversePatches...) { if p.Regex != "" { parsed, err := patchesregex.PrepareRegex(p.Regex) diff --git a/pkg/controllers/generic/import_syncer.go b/pkg/controllers/generic/import_syncer.go index e939bb88a..418f6a528 100644 --- a/pkg/controllers/generic/import_syncer.go +++ b/pkg/controllers/generic/import_syncer.go @@ -7,11 +7,11 @@ import ( "strings" "time" - "github.com/loft-sh/vcluster/pkg/options" + vclusterconfig "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" - "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/constants" "github.com/loft-sh/vcluster/pkg/controllers/syncer" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" @@ -33,13 +33,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func CreateImporters(ctx *options.ControllerContext, cfg *config.Config) error { +func CreateImporters(ctx *config.ControllerContext) error { + cfg := ctx.Config.Experimental.GenericSync if len(cfg.Imports) == 0 { return nil } registerCtx := util.ToRegisterContext(ctx) - if !registerCtx.Options.MultiNamespaceMode { + if !registerCtx.Config.Experimental.MultiNamespaceMode.Enabled { return fmt.Errorf("invalid configuration, 'import' type sync of the generic CRDs is allowed only in the multi-namespace mode") } @@ -84,7 +85,7 @@ func CreateImporters(ctx *options.ControllerContext, cfg *config.Config) error { return nil } -func createImporter(ctx *synccontext.RegisterContext, config *config.Import, gvkRegister GVKRegister) (syncertypes.Syncer, error) { +func createImporter(ctx *synccontext.RegisterContext, config *vclusterconfig.Import, gvkRegister GVKRegister) (syncertypes.Syncer, error) { gvk := schema.FromAPIVersionAndKind(config.APIVersion, config.Kind) controllerID := fmt.Sprintf("%s/%s/GenericImport", strings.ToLower(gvk.Kind), strings.ToLower(gvk.GroupVersion().String())) @@ -116,7 +117,7 @@ type importer struct { translator.Translator virtualClient client.Client patcher *patcher - config *config.Import + config *vclusterconfig.Import syncerOptions *syncertypes.Options gvk schema.GroupVersionKind name string diff --git a/pkg/controllers/generic/patcher.go b/pkg/controllers/generic/patcher.go index 5ef9145fe..07521f910 100644 --- a/pkg/controllers/generic/patcher.go +++ b/pkg/controllers/generic/patcher.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/loft-sh/vcluster/pkg/config" + "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/log" "github.com/loft-sh/vcluster/pkg/patches" "github.com/pkg/errors" diff --git a/pkg/controllers/k8sdefaultendpoint/k8sdefaultendpoint.go b/pkg/controllers/k8sdefaultendpoint/k8sdefaultendpoint.go index 23df1281c..e3c93c350 100644 --- a/pkg/controllers/k8sdefaultendpoint/k8sdefaultendpoint.go +++ b/pkg/controllers/k8sdefaultendpoint/k8sdefaultendpoint.go @@ -5,8 +5,8 @@ import ( "fmt" "time" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/constants" - "github.com/loft-sh/vcluster/pkg/options" "sigs.k8s.io/controller-runtime/pkg/controller" "github.com/loft-sh/vcluster/pkg/specialservices" @@ -45,11 +45,11 @@ type EndpointController struct { provider provider } -func NewEndpointController(ctx *options.ControllerContext, provider provider) *EndpointController { +func NewEndpointController(ctx *config.ControllerContext, provider provider) *EndpointController { return &EndpointController{ LocalClient: ctx.LocalManager.GetClient(), VirtualClient: ctx.VirtualManager.GetClient(), - ServiceName: ctx.Options.ServiceName, + ServiceName: ctx.Config.ServiceName, ServiceNamespace: ctx.CurrentNamespace, VirtualManagerCache: ctx.VirtualManager.GetCache(), Log: loghelper.New("kubernetes-default-endpoint-controller"), diff --git a/pkg/controllers/k8sdefaultendpoint/register.go b/pkg/controllers/k8sdefaultendpoint/register.go index d9e1bb224..fc6245169 100644 --- a/pkg/controllers/k8sdefaultendpoint/register.go +++ b/pkg/controllers/k8sdefaultendpoint/register.go @@ -1,13 +1,13 @@ package k8sdefaultendpoint import ( - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/discovery" "k8s.io/klog/v2" ) -func Register(ctx *options.ControllerContext) error { +func Register(ctx *config.ControllerContext) error { discoveryClient, err := discovery.NewDiscoveryClientForConfig(ctx.VirtualManager.GetConfig()) if err != nil { return err diff --git a/pkg/controllers/register.go b/pkg/controllers/register.go index 574826b12..4af14967c 100644 --- a/pkg/controllers/register.go +++ b/pkg/controllers/register.go @@ -1,49 +1,23 @@ package controllers import ( - "context" "fmt" "net/http" - "os" "strings" - "time" - - "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd" - "github.com/loft-sh/vcluster/pkg/options" - "github.com/loft-sh/vcluster/pkg/util/kubeconfig" - "github.com/loft-sh/vcluster/pkg/util/translate" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/klog/v2" + vclusterconfig "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/config" + "github.com/loft-sh/vcluster/pkg/controllers/deploy" "github.com/loft-sh/vcluster/pkg/controllers/generic" - "github.com/loft-sh/vcluster/pkg/controllers/servicesync" - "github.com/loft-sh/vcluster/pkg/controllers/syncer" - "github.com/loft-sh/vcluster/pkg/helm" - "github.com/loft-sh/vcluster/pkg/util/blockingcacheclient" - util "github.com/loft-sh/vcluster/pkg/util/context" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - - "github.com/loft-sh/vcluster/pkg/controllers/k8sdefaultendpoint" - "github.com/loft-sh/vcluster/pkg/controllers/manifests" + "github.com/loft-sh/vcluster/pkg/controllers/resources/configmaps" "github.com/loft-sh/vcluster/pkg/controllers/resources/csidrivers" "github.com/loft-sh/vcluster/pkg/controllers/resources/csinodes" "github.com/loft-sh/vcluster/pkg/controllers/resources/csistoragecapacities" - "github.com/loft-sh/vcluster/pkg/controllers/resources/ingressclasses" - "github.com/loft-sh/vcluster/pkg/controllers/resources/namespaces" - "github.com/loft-sh/vcluster/pkg/controllers/resources/serviceaccounts" - - "github.com/loft-sh/log" - "github.com/loft-sh/vcluster/pkg/controllers/coredns" - "github.com/loft-sh/vcluster/pkg/controllers/podsecurity" - "github.com/loft-sh/vcluster/pkg/controllers/resources/configmaps" "github.com/loft-sh/vcluster/pkg/controllers/resources/endpoints" "github.com/loft-sh/vcluster/pkg/controllers/resources/events" + "github.com/loft-sh/vcluster/pkg/controllers/resources/ingressclasses" "github.com/loft-sh/vcluster/pkg/controllers/resources/ingresses" + "github.com/loft-sh/vcluster/pkg/controllers/resources/namespaces" "github.com/loft-sh/vcluster/pkg/controllers/resources/networkpolicies" "github.com/loft-sh/vcluster/pkg/controllers/resources/nodes" "github.com/loft-sh/vcluster/pkg/controllers/resources/persistentvolumeclaims" @@ -52,11 +26,25 @@ import ( "github.com/loft-sh/vcluster/pkg/controllers/resources/pods" "github.com/loft-sh/vcluster/pkg/controllers/resources/priorityclasses" "github.com/loft-sh/vcluster/pkg/controllers/resources/secrets" - "github.com/loft-sh/vcluster/pkg/controllers/resources/services" + "github.com/loft-sh/vcluster/pkg/controllers/resources/serviceaccounts" "github.com/loft-sh/vcluster/pkg/controllers/resources/storageclasses" "github.com/loft-sh/vcluster/pkg/controllers/resources/volumesnapshots/volumesnapshotclasses" "github.com/loft-sh/vcluster/pkg/controllers/resources/volumesnapshots/volumesnapshotcontents" "github.com/loft-sh/vcluster/pkg/controllers/resources/volumesnapshots/volumesnapshots" + "github.com/loft-sh/vcluster/pkg/controllers/servicesync" + "github.com/loft-sh/vcluster/pkg/controllers/syncer" + "github.com/loft-sh/vcluster/pkg/util/blockingcacheclient" + util "github.com/loft-sh/vcluster/pkg/util/context" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + "github.com/loft-sh/vcluster/pkg/controllers/coredns" + "github.com/loft-sh/vcluster/pkg/controllers/k8sdefaultendpoint" + "github.com/loft-sh/vcluster/pkg/controllers/podsecurity" + "github.com/loft-sh/vcluster/pkg/controllers/resources/services" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" syncertypes "github.com/loft-sh/vcluster/pkg/types" "github.com/loft-sh/vcluster/pkg/util/loghelper" @@ -64,58 +52,67 @@ import ( "golang.org/x/sync/errgroup" ) -var ResourceControllers = map[string][]func(*synccontext.RegisterContext) (syncertypes.Object, error){ - "services": {services.New}, - "configmaps": {configmaps.New}, - "secrets": {secrets.New}, - "endpoints": {endpoints.New}, - "pods": {pods.New}, - "events": {events.New}, - "persistentvolumeclaims": {persistentvolumeclaims.New}, - "ingresses": {ingresses.New}, - "ingressclasses": {ingressclasses.New}, - "storageclasses": {storageclasses.New}, - "hoststorageclasses": {storageclasses.NewHostStorageClassSyncer}, - "priorityclasses": {priorityclasses.New}, - "nodes,fake-nodes": {nodes.New}, - "poddisruptionbudgets": {poddisruptionbudgets.New}, - "networkpolicies": {networkpolicies.New}, - "volumesnapshots": {volumesnapshotclasses.New, volumesnapshots.New, volumesnapshotcontents.New}, - "serviceaccounts": {serviceaccounts.New}, - "csinodes": {csinodes.New}, - "csidrivers": {csidrivers.New}, - "csistoragecapacities": {csistoragecapacities.New}, - "namespaces": {namespaces.New}, - "persistentvolumes,fake-persistentvolumes": {persistentvolumes.New}, +type initFunction func(*synccontext.RegisterContext) (syncertypes.Object, error) + +func getSyncers(ctx *config.ControllerContext) []initFunction { + return []initFunction{ + isEnabled(ctx.Config.Sync.ToHost.Services.Enabled, services.New), + isEnabled(ctx.Config.Sync.ToHost.ConfigMaps.Enabled, configmaps.New), + isEnabled(ctx.Config.Sync.ToHost.Secrets.Enabled, secrets.New), + isEnabled(ctx.Config.Sync.ToHost.Endpoints.Enabled, endpoints.New), + isEnabled(ctx.Config.Sync.ToHost.Pods.Enabled, pods.New), + isEnabled(ctx.Config.Sync.FromHost.Events.Enabled, events.New), + isEnabled(ctx.Config.Sync.ToHost.PersistentVolumeClaims.Enabled, persistentvolumeclaims.New), + isEnabled(ctx.Config.Sync.ToHost.Ingresses.Enabled, ingresses.New), + isEnabled(ctx.Config.Sync.FromHost.IngressClasses.Enabled, ingressclasses.New), + isEnabled(ctx.Config.Sync.ToHost.StorageClasses.Enabled, storageclasses.New), + isEnabled(ctx.Config.Sync.FromHost.StorageClasses.Enabled, storageclasses.NewHostStorageClassSyncer), + isEnabled(ctx.Config.Sync.ToHost.PriorityClasses.Enabled, priorityclasses.New), + isEnabled(ctx.Config.Sync.ToHost.PodDisruptionBudgets.Enabled, poddisruptionbudgets.New), + isEnabled(ctx.Config.Sync.ToHost.NetworkPolicies.Enabled, networkpolicies.New), + isEnabled(ctx.Config.Sync.ToHost.VolumeSnapshots.Enabled, volumesnapshotclasses.New), + isEnabled(ctx.Config.Sync.ToHost.VolumeSnapshots.Enabled, volumesnapshots.New), + isEnabled(ctx.Config.Sync.ToHost.VolumeSnapshots.Enabled, volumesnapshotcontents.New), + isEnabled(ctx.Config.Sync.ToHost.ServiceAccounts.Enabled, serviceaccounts.New), + isEnabled(ctx.Config.Sync.FromHost.CSINodes.Enabled, csinodes.New), + isEnabled(ctx.Config.Sync.FromHost.CSIDrivers.Enabled, csidrivers.New), + isEnabled(ctx.Config.Sync.FromHost.CSIStorageCapacities.Enabled, csistoragecapacities.New), + isEnabled(ctx.Config.Experimental.MultiNamespaceMode.Enabled, namespaces.New), + persistentvolumes.New, + nodes.New, + } } -func Create(ctx *options.ControllerContext) ([]syncertypes.Object, error) { +func isEnabled(enabled bool, fn initFunction) initFunction { + if enabled { + return fn + } + return nil +} + +func Create(ctx *config.ControllerContext) ([]syncertypes.Object, error) { registerContext := util.ToRegisterContext(ctx) // register controllers for resource synchronization syncers := []syncertypes.Object{} - for k, v := range ResourceControllers { - for _, controllerNew := range v { - controllers := strings.Split(k, ",") - for _, controller := range controllers { - if ctx.Controllers.Has(controller) { - loghelper.Infof("Start %s sync controller", controller) - ctrl, err := controllerNew(registerContext) - if err != nil { - return nil, errors.Wrapf(err, "register %s controller", controller) - } - - syncers = append(syncers, ctrl) - break - } - } + for _, newSyncer := range getSyncers(ctx) { + if newSyncer == nil { + continue + } + + createdController, err := newSyncer(registerContext) + if err != nil { + return nil, errors.Wrapf(err, "register %s controller", createdController.Name()) } + + loghelper.Infof("Start %s sync controller", createdController.Name()) + syncers = append(syncers, createdController) } return syncers, nil } -func ExecuteInitializers(controllerCtx *options.ControllerContext, syncers []syncertypes.Object) error { +func ExecuteInitializers(controllerCtx *config.ControllerContext, syncers []syncertypes.Object) error { registerContext := util.ToRegisterContext(controllerCtx) // execute in parallel because each one might be time-consuming @@ -138,7 +135,7 @@ func ExecuteInitializers(controllerCtx *options.ControllerContext, syncers []syn return errorGroup.Wait() } -func RegisterIndices(ctx *options.ControllerContext, syncers []syncertypes.Object) error { +func RegisterIndices(ctx *config.ControllerContext, syncers []syncertypes.Object) error { registerContext := util.ToRegisterContext(ctx) for _, s := range syncers { indexRegisterer, ok := s.(syncertypes.IndicesRegisterer) @@ -153,7 +150,7 @@ func RegisterIndices(ctx *options.ControllerContext, syncers []syncertypes.Objec return nil } -func RegisterControllers(ctx *options.ControllerContext, syncers []syncertypes.Object) error { +func RegisterControllers(ctx *config.ControllerContext, syncers []syncertypes.Object) error { registerContext := util.ToRegisterContext(ctx) err := k8sdefaultendpoint.Register(ctx) @@ -162,7 +159,7 @@ func RegisterControllers(ctx *options.ControllerContext, syncers []syncertypes.O } // register controller that maintains pod security standard check - if ctx.Options.EnforcePodSecurityStandard != "" { + if ctx.Config.Policies.PodSecurityStandard != "" { err := RegisterPodSecurityController(ctx) if err != nil { return err @@ -176,7 +173,7 @@ func RegisterControllers(ctx *options.ControllerContext, syncers []syncertypes.O } // register init manifests configmap watcher controller - err = RegisterInitManifestsController(ctx) + err = deploy.RegisterInitManifestsController(ctx) if err != nil { return err } @@ -218,92 +215,28 @@ func RegisterControllers(ctx *options.ControllerContext, syncers []syncertypes.O return nil } -func RegisterGenericSyncController(ctx *options.ControllerContext) error { - // first check if a generic CRD config is provided and we actually need - // to create any of these syncer controllers - c := os.Getenv(options.GenericConfig) - if strings.TrimSpace(c) == "" || strings.TrimSpace(c) == "---" { - // empty configuration, no need for creating any syncer controllers - loghelper.Infof("no generic config provided, skipping creating controllers") - - return nil - } - - configuration, err := config.Parse(c) - if err != nil { - loghelper.Infof("error parsing the config %v", err.Error()) - return errors.Wrapf(err, "parsing the config") - } - - loghelper.Infof("generic config provided, parsed successfully") - - err = generic.CreateExporters(ctx, configuration) - if err != nil { - return err - } - - err = generic.CreateImporters(ctx, configuration) - if err != nil { - return err - } - - return nil -} - -func RegisterInitManifestsController(controllerCtx *options.ControllerContext) error { - vconfig, err := kubeconfig.ConvertRestConfigToClientConfig(controllerCtx.VirtualManager.GetConfig()) - if err != nil { - return err - } - - vConfigRaw, err := vconfig.RawConfig() +func RegisterGenericSyncController(ctx *config.ControllerContext) error { + err := generic.CreateExporters(ctx) if err != nil { return err } - helmBinaryPath, err := cmd.GetHelmBinaryPath(controllerCtx.Context, log.GetInstance()) + err = generic.CreateImporters(ctx) if err != nil { return err } - controller := &manifests.InitManifestsConfigMapReconciler{ - LocalClient: controllerCtx.CurrentNamespaceClient, - Log: loghelper.New("init-manifests-controller"), - VirtualManager: controllerCtx.VirtualManager, - - HelmClient: helm.NewClient(&vConfigRaw, log.GetInstance(), helmBinaryPath), - } - - go func() { - wait.JitterUntilWithContext(controllerCtx.Context, func(ctx context.Context) { - for { - result, err := controller.Reconcile(ctx, ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: controllerCtx.CurrentNamespace, - Name: translate.VClusterName + manifests.InitManifestSuffix, - }, - }) - if err != nil { - klog.Errorf("Error reconciling init_configmap: %v", err) - break - } else if !result.Requeue { - break - } - } - }, time.Second*10, 1.0, true) - }() - return nil } -func RegisterServiceSyncControllers(ctx *options.ControllerContext) error { - hostNamespace := ctx.Options.TargetNamespace - if ctx.Options.MultiNamespaceMode { +func RegisterServiceSyncControllers(ctx *config.ControllerContext) error { + hostNamespace := ctx.Config.TargetNamespace + if ctx.Config.Experimental.MultiNamespaceMode.Enabled { hostNamespace = ctx.CurrentNamespace } - if len(ctx.Options.MapHostServices) > 0 { - mapping, err := parseMapping(ctx.Options.MapHostServices, hostNamespace, "") + if len(ctx.Config.Networking.ReplicateServices.FromHost) > 0 { + mapping, err := parseMapping(ctx.Config.Networking.ReplicateServices.FromHost, hostNamespace, "") if err != nil { return errors.Wrap(err, "parse physical service mapping") } @@ -349,8 +282,8 @@ func RegisterServiceSyncControllers(ctx *options.ControllerContext) error { } } - if len(ctx.Options.MapVirtualServices) > 0 { - mapping, err := parseMapping(ctx.Options.MapVirtualServices, "", hostNamespace) + if len(ctx.Config.Networking.ReplicateServices.ToHost) > 0 { + mapping, err := parseMapping(ctx.Config.Networking.ReplicateServices.ToHost, "", hostNamespace) if err != nil { return errors.Wrap(err, "parse physical service mapping") } @@ -363,7 +296,7 @@ func RegisterServiceSyncControllers(ctx *options.ControllerContext) error { Log: loghelper.New("map-virtual-service-syncer"), } - if ctx.Options.MultiNamespaceMode { + if ctx.Config.Experimental.MultiNamespaceMode.Enabled { controller.CreateEndpoints = true } @@ -376,43 +309,39 @@ func RegisterServiceSyncControllers(ctx *options.ControllerContext) error { return nil } -func parseMapping(mappings []string, fromDefaultNamespace, toDefaultNamespace string) (map[string]types.NamespacedName, error) { +func parseMapping(mappings []vclusterconfig.ServiceMapping, fromDefaultNamespace, toDefaultNamespace string) (map[string]types.NamespacedName, error) { ret := map[string]types.NamespacedName{} for _, m := range mappings { - splitted := strings.Split(m, "=") - if len(splitted) != 2 { - return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=service2") - } else if len(splitted[0]) == 0 || len(splitted[1]) == 0 { - return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=service2") - } + from := m.From + to := m.To - fromSplitted := strings.Split(splitted[0], "/") + fromSplitted := strings.Split(from, "/") if len(fromSplitted) == 1 { if fromDefaultNamespace == "" { return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=service2") } - splitted[0] = fromDefaultNamespace + "/" + splitted[0] + from = fromDefaultNamespace + "/" + from } else if len(fromSplitted) != 2 { return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=service2") } - toSplitted := strings.Split(splitted[1], "/") + toSplitted := strings.Split(to, "/") if len(toSplitted) == 1 { if toDefaultNamespace == "" { return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=namespace2/service2") } - ret[splitted[0]] = types.NamespacedName{ + ret[from] = types.NamespacedName{ Namespace: toDefaultNamespace, - Name: splitted[1], + Name: to, } } else if len(toSplitted) == 2 { if toDefaultNamespace != "" { return nil, fmt.Errorf("invalid service mapping, please use namespace1/service1=service2") } - ret[splitted[0]] = types.NamespacedName{ + ret[from] = types.NamespacedName{ Namespace: toSplitted[0], Name: toSplitted[1], } @@ -424,7 +353,7 @@ func parseMapping(mappings []string, fromDefaultNamespace, toDefaultNamespace st return ret, nil } -func RegisterCoreDNSController(ctx *options.ControllerContext) error { +func RegisterCoreDNSController(ctx *config.ControllerContext) error { controller := &coredns.NodeHostsReconciler{ Client: ctx.VirtualManager.GetClient(), Log: loghelper.New("corednsnodehosts-controller"), @@ -436,10 +365,10 @@ func RegisterCoreDNSController(ctx *options.ControllerContext) error { return nil } -func RegisterPodSecurityController(ctx *options.ControllerContext) error { +func RegisterPodSecurityController(ctx *config.ControllerContext) error { controller := &podsecurity.Reconciler{ Client: ctx.VirtualManager.GetClient(), - PodSecurityStandard: ctx.Options.EnforcePodSecurityStandard, + PodSecurityStandard: ctx.Config.Policies.PodSecurityStandard, Log: loghelper.New("podSecurity-controller"), } err := controller.SetupWithManager(ctx.VirtualManager) diff --git a/pkg/controllers/resources/configmaps/syncer.go b/pkg/controllers/resources/configmaps/syncer.go index ae71c0e29..cf38c5f4b 100644 --- a/pkg/controllers/resources/configmaps/syncer.go +++ b/pkg/controllers/resources/configmaps/syncer.go @@ -27,7 +27,7 @@ func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { return &configMapSyncer{ NamespacedTranslator: t, - syncAllConfigMaps: ctx.Options.SyncAllConfigMaps, + syncAllConfigMaps: ctx.Config.Sync.ToHost.ConfigMaps.All, }, nil } diff --git a/pkg/controllers/resources/csistoragecapacities/syncer.go b/pkg/controllers/resources/csistoragecapacities/syncer.go index 6255b66b1..c2c3bdcab 100644 --- a/pkg/controllers/resources/csistoragecapacities/syncer.go +++ b/pkg/controllers/resources/csistoragecapacities/syncer.go @@ -22,8 +22,8 @@ import ( func New(ctx *synccontext.RegisterContext) (syncertypes.Object, error) { return &csistoragecapacitySyncer{ - storageClassSyncEnabled: ctx.Controllers.Has("storageclasses"), - hostStorageClassSyncEnabled: ctx.Controllers.Has("hoststorageclasses"), + storageClassSyncEnabled: ctx.Config.Sync.ToHost.StorageClasses.Enabled, + hostStorageClassSyncEnabled: ctx.Config.Sync.FromHost.StorageClasses.Enabled, physicalClient: ctx.PhysicalManager.GetClient(), }, nil } diff --git a/pkg/controllers/resources/csistoragecapacities/syncer_test.go b/pkg/controllers/resources/csistoragecapacities/syncer_test.go index b7fef3e4e..cb26fead7 100644 --- a/pkg/controllers/resources/csistoragecapacities/syncer_test.go +++ b/pkg/controllers/resources/csistoragecapacities/syncer_test.go @@ -103,8 +103,8 @@ func TestSyncHostStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Insert("hoststorageclasses") - ctx.Controllers.Delete("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = true + ctx.Config.Sync.ToHost.StorageClasses.Enabled = false syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, New) _, err := sync.(*csistoragecapacitySyncer).SyncToVirtual(syncCtx, pObj) assert.NilError(t, err) @@ -116,8 +116,8 @@ func TestSyncHostStorageClass(t *testing.T) { ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{}, ExpectedPhysicalState: map[schema.GroupVersionKind][]runtime.Object{}, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Insert("hoststorageclasses") - ctx.Controllers.Delete("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = true + ctx.Config.Sync.ToHost.StorageClasses.Enabled = false syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, New) _, err := sync.(*csistoragecapacitySyncer).SyncToHost(syncCtx, vObj) assert.NilError(t, err) @@ -134,8 +134,8 @@ func TestSyncHostStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObjUpdated}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Insert("hoststorageclasses") - ctx.Controllers.Delete("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = true + ctx.Config.Sync.ToHost.StorageClasses.Enabled = false syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, New) _, err := sync.(*csistoragecapacitySyncer).Sync(syncCtx, pObjUpdated, vObj) assert.NilError(t, err) @@ -242,8 +242,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -267,8 +267,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -292,8 +292,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -312,8 +312,8 @@ func TestSyncStorageClass(t *testing.T) { ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{}, ExpectedPhysicalState: map[schema.GroupVersionKind][]runtime.Object{}, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -337,8 +337,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObjUpdated}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -362,8 +362,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) @@ -387,8 +387,8 @@ func TestSyncStorageClass(t *testing.T) { storagev1.SchemeGroupVersion.WithKind(kind): {pObj}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Controllers.Delete("hoststorageclasses") - ctx.Controllers.Insert("storageclasses") + ctx.Config.Sync.FromHost.StorageClasses.Enabled = false + ctx.Config.Sync.ToHost.StorageClasses.Enabled = true var err error syncCtx, sync := generictesting.FakeStartSyncer(t, ctx, storageclasses.New) _, err = sync.(syncer.Syncer).SyncToHost(syncCtx, vSCa) diff --git a/pkg/controllers/resources/namespaces/syncer.go b/pkg/controllers/resources/namespaces/syncer.go index c38f8fa3c..f4a0d81df 100644 --- a/pkg/controllers/resources/namespaces/syncer.go +++ b/pkg/controllers/resources/namespaces/syncer.go @@ -1,9 +1,6 @@ package namespaces import ( - "fmt" - "strings" - "github.com/loft-sh/vcluster/pkg/constants" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" "github.com/loft-sh/vcluster/pkg/controllers/syncer/translator" @@ -11,8 +8,6 @@ import ( "github.com/loft-sh/vcluster/pkg/util/translate" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/validation" - "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -26,22 +21,21 @@ var excludedAnnotations = []string{ } const ( - VclusterNameAnnotation = "vcluster.loft.sh/vcluster-name" - VclusterNamespaceAnnotation = "vcluster.loft.sh/vcluster-namespace" + VClusterNameAnnotation = "vcluster.loft.sh/vcluster-name" + VClusterNamespaceAnnotation = "vcluster.loft.sh/vcluster-namespace" ) func New(ctx *synccontext.RegisterContext) (syncertypes.Object, error) { - namespaceLabels, err := parseNamespaceLabels(ctx.Options.NamespaceLabels) - if err != nil { - return nil, fmt.Errorf("invalid value of the namespace-labels flag: %w", err) + namespaceLabels := map[string]string{} + for k, v := range ctx.Config.Experimental.MultiNamespaceMode.NamespaceLabels { + namespaceLabels[k] = v } - - namespaceLabels[VclusterNameAnnotation] = ctx.Options.Name - namespaceLabels[VclusterNamespaceAnnotation] = ctx.CurrentNamespace + namespaceLabels[VClusterNameAnnotation] = ctx.Config.Name + namespaceLabels[VClusterNamespaceAnnotation] = ctx.CurrentNamespace return &namespaceSyncer{ Translator: translator.NewClusterTranslator(ctx, "namespace", &corev1.Namespace{}, NamespaceNameTranslator, excludedAnnotations...), - workloadServiceAccountName: ctx.Options.ServiceAccount, + workloadServiceAccountName: ctx.Config.ControlPlane.Advanced.WorkloadServiceAccount.Name, namespaceLabels: namespaceLabels, }, nil } @@ -106,20 +100,3 @@ func (s *namespaceSyncer) EnsureWorkloadServiceAccount(ctx *synccontext.SyncCont func NamespaceNameTranslator(vName string, _ client.Object) string { return translate.Default.PhysicalNamespace(vName) } - -func parseNamespaceLabels(labels []string) (map[string]string, error) { - out := map[string]string{} - for _, v := range labels { - parts := strings.SplitN(v, "=", 2) - if len(parts) != 2 { - return nil, fmt.Errorf("incorrect format, expected: key=value got: %s", v) - } - out[parts[0]] = parts[1] - } - errs := validation.ValidateLabels(out, field.NewPath("namespace-labels")) - if len(errs) != 0 { - return nil, fmt.Errorf("invalid labels: %v", errs) - } - - return out, nil -} diff --git a/pkg/controllers/resources/nodes/fake_syncer.go b/pkg/controllers/resources/nodes/fake_syncer.go index 32dc0db10..8cbf6eb68 100644 --- a/pkg/controllers/resources/nodes/fake_syncer.go +++ b/pkg/controllers/resources/nodes/fake_syncer.go @@ -35,7 +35,7 @@ var ( func NewFakeSyncer(ctx *synccontext.RegisterContext, nodeService nodeservice.Provider) (syncer.Object, error) { return &fakeNodeSyncer{ nodeServiceProvider: nodeService, - fakeKubeletIPs: ctx.Options.FakeKubeletIPs, + fakeKubeletIPs: ctx.Config.Networking.Advanced.ProxyKubelets.ByIP, }, nil } @@ -144,11 +144,13 @@ func newGUID() string { return random.String(8) + "-" + random.String(4) + "-" + random.String(4) + "-" + random.String(4) + "-" + random.String(12) } -func CreateFakeNode(ctx context.Context, +func CreateFakeNode( + ctx context.Context, fakeKubeletIPs bool, nodeServiceProvider nodeservice.Provider, virtualClient client.Client, - name string) error { + name string, +) error { nodeServiceProvider.Lock() defer nodeServiceProvider.Unlock() diff --git a/pkg/controllers/resources/nodes/fake_syncer_test.go b/pkg/controllers/resources/nodes/fake_syncer_test.go index c7cf7b2ff..9c00cb3ec 100644 --- a/pkg/controllers/resources/nodes/fake_syncer_test.go +++ b/pkg/controllers/resources/nodes/fake_syncer_test.go @@ -144,13 +144,14 @@ func TestFakeSync(t *testing.T) { generictesting.RunTests(t, []*generictesting.SyncTest{ { - Name: "Create", + Name: "Create test", InitialVirtualState: []runtime.Object{basePod}, ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{ corev1.SchemeGroupVersion.WithKind("Node"): {baseNode}, corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncContext, syncer := newFakeFakeSyncer(t, ctx) _, err := syncer.FakeSyncToVirtual(syncContext, baseName) assert.NilError(t, err) @@ -172,15 +173,17 @@ func TestFakeSync(t *testing.T) { node.Status.NodeInfo.KubeletVersion = fakeGUID } - node1.Status.Addresses[0].Address = node2.Status.Addresses[0].Address + node1.Status.Images = node2.Status.Images obj1 = node1 obj2 = node2 } + + assert.DeepEqual(t, obj1, obj2) return apiequality.Semantic.DeepEqual(obj1, obj2) }, }, { - Name: "Delete", + Name: "Delete test", InitialVirtualState: []runtime.Object{baseNode}, ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{ corev1.SchemeGroupVersion.WithKind("Node"): {}, diff --git a/pkg/controllers/resources/nodes/register.go b/pkg/controllers/resources/nodes/register.go index 0696946ee..bc68bc95e 100644 --- a/pkg/controllers/resources/nodes/register.go +++ b/pkg/controllers/resources/nodes/register.go @@ -16,8 +16,8 @@ func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { return nil, err } - nodeService := nodeservice.NewNodeServiceProvider(ctx.Options.ServiceName, ctx.CurrentNamespace, ctx.CurrentNamespaceClient, ctx.VirtualManager.GetClient(), uncachedVirtualClient) - if !ctx.Controllers.Has("nodes") { + nodeService := nodeservice.NewNodeServiceProvider(ctx.Config.ServiceName, ctx.CurrentNamespace, ctx.CurrentNamespaceClient, ctx.VirtualManager.GetClient(), uncachedVirtualClient) + if !ctx.Config.Sync.FromHost.Nodes.Enabled { return NewFakeSyncer(ctx, nodeService) } diff --git a/pkg/controllers/resources/nodes/syncer.go b/pkg/controllers/resources/nodes/syncer.go index 8e61599f3..0f59f8d29 100644 --- a/pkg/controllers/resources/nodes/syncer.go +++ b/pkg/controllers/resources/nodes/syncer.go @@ -31,21 +31,17 @@ import ( ) func NewSyncer(ctx *synccontext.RegisterContext, nodeServiceProvider nodeservice.Provider) (syncertypes.Object, error) { - var err error var nodeSelector labels.Selector - if ctx.Options.SyncAllNodes { + if ctx.Config.Sync.FromHost.Nodes.Selector.All { nodeSelector = labels.Everything() - } else if ctx.Options.NodeSelector != "" { - nodeSelector, err = labels.Parse(ctx.Options.NodeSelector) - if err != nil { - return nil, errors.Wrap(err, "parse node selector") - } + } else if len(ctx.Config.Sync.FromHost.Nodes.Selector.Labels) > 0 { + nodeSelector = labels.Set(ctx.Config.Sync.FromHost.Nodes.Selector.Labels).AsSelector() } // parse tolerations var tolerations []*corev1.Toleration - if len(ctx.Options.Tolerations) > 0 { - for _, t := range ctx.Options.Tolerations { + if len(ctx.Config.Sync.ToHost.Pods.EnforceTolerations) > 0 { + for _, t := range ctx.Config.Sync.ToHost.Pods.EnforceTolerations { tol, err := toleration.ParseToleration(t) if err == nil { tolerations = append(tolerations, &tol) @@ -54,13 +50,13 @@ func NewSyncer(ctx *synccontext.RegisterContext, nodeServiceProvider nodeservice } return &nodeSyncer{ - enableScheduler: ctx.Options.EnableScheduler, + enableScheduler: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled, - enforceNodeSelector: ctx.Options.EnforceNodeSelector, + enforceNodeSelector: true, nodeSelector: nodeSelector, - clearImages: ctx.Options.ClearNodeImages, - useFakeKubelets: !ctx.Options.DisableFakeKubelets, - fakeKubeletIPs: ctx.Options.FakeKubeletIPs, + clearImages: ctx.Config.Sync.FromHost.Nodes.ClearImageStatus, + useFakeKubelets: ctx.Config.Networking.Advanced.ProxyKubelets.ByHostname || ctx.Config.Networking.Advanced.ProxyKubelets.ByIP, + fakeKubeletIPs: ctx.Config.Networking.Advanced.ProxyKubelets.ByIP, physicalClient: ctx.PhysicalManager.GetClient(), virtualClient: ctx.VirtualManager.GetClient(), diff --git a/pkg/controllers/resources/nodes/syncer_test.go b/pkg/controllers/resources/nodes/syncer_test.go index 86eb33a35..a97d8054a 100644 --- a/pkg/controllers/resources/nodes/syncer_test.go +++ b/pkg/controllers/resources/nodes/syncer_test.go @@ -3,9 +3,6 @@ package nodes import ( "testing" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" syncertypes "github.com/loft-sh/vcluster/pkg/types" "github.com/loft-sh/vcluster/pkg/util/translate" @@ -142,6 +139,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, editedNode, baseNode) assert.NilError(t, err) @@ -161,6 +159,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseVNode) assert.NilError(t, err) @@ -253,7 +252,8 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.Tolerations = []string{"key1=value1:NoSchedule"} + ctx.Config.Sync.ToHost.Pods.EnforceTolerations = []string{"key1=value1:NoSchedule"} + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -268,7 +268,8 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.Tolerations = []string{"key2=value2:NoSchedule"} + ctx.Config.Sync.ToHost.Pods.EnforceTolerations = []string{"key2=value2:NoSchedule"} + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -283,7 +284,8 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.Tolerations = []string{":NoSchedule op=Exists"} + ctx.Config.Sync.ToHost.Pods.EnforceTolerations = []string{":NoSchedule op=Exists"} + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -375,10 +377,10 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Node"): {editedNode}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforceNodeSelector = false - req, _ := labels.NewRequirement("test", selection.Equals, []string{"true"}) - sel := labels.NewSelector().Add(*req) - ctx.Options.NodeSelector = sel.String() + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false + ctx.Config.Sync.FromHost.Nodes.Selector.Labels = map[string]string{ + "test": "true", + } syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -393,10 +395,10 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforceNodeSelector = false - req, _ := labels.NewRequirement("test", selection.NotEquals, []string{"true"}) - sel := labels.NewSelector().Add(*req) - ctx.Options.NodeSelector = sel.String() + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false + ctx.Config.Sync.FromHost.Nodes.Selector.Labels = map[string]string{ + "test": "true", + } syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -411,7 +413,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {basePod}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforceNodeSelector = false + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -423,10 +425,10 @@ func TestSync(t *testing.T) { InitialVirtualState: []runtime.Object{baseVNode}, ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{}, Sync: func(ctx *synccontext.RegisterContext) { - req, _ := labels.NewRequirement("test", selection.NotEquals, []string{"true"}) - sel := labels.NewSelector().Add(*req) - ctx.Options.NodeSelector = sel.String() - ctx.Options.EnforceNodeSelector = true + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false + ctx.Config.Sync.FromHost.Nodes.Selector.Labels = map[string]string{ + "test": "true", + } syncCtx, syncer := newFakeSyncer(t, ctx) _, err := syncer.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -517,8 +519,9 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Node"): {editedNode}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.SyncAllNodes = true - ctx.Options.ClearNodeImages = true + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false + ctx.Config.Sync.FromHost.Nodes.Selector.All = true + ctx.Config.Sync.FromHost.Nodes.ClearImageStatus = true syncCtx, syncerSvc := newFakeSyncer(t, ctx) _, err := syncerSvc.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) @@ -568,7 +571,8 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Node"): {editedNode}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.SyncAllNodes = true + ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = false + ctx.Config.Sync.FromHost.Nodes.Selector.All = true syncCtx, syncerSvc := newFakeSyncer(t, ctx) _, err := syncerSvc.Sync(syncCtx, baseNode, baseNode) assert.NilError(t, err) diff --git a/pkg/controllers/resources/nodes/translate.go b/pkg/controllers/resources/nodes/translate.go index ab44516d1..8925dc055 100644 --- a/pkg/controllers/resources/nodes/translate.go +++ b/pkg/controllers/resources/nodes/translate.go @@ -111,8 +111,8 @@ func (s *nodeSyncer) translateUpdateBackwards(pNode *corev1.Node, vNode *corev1. } // add annotation to prevent scale down of node by cluster-autoscaler - // the env var VCLUSTER_NODE_NAME is set when only one replica of vcluster is running - if nodeName, set := os.LookupEnv("VCLUSTER_NODE_NAME"); set && nodeName == pNode.Name { + // the env var NODE_NAME is set when only one replica of vcluster is running + if nodeName, set := os.LookupEnv("NODE_NAME"); set && nodeName == pNode.Name { annotations["cluster-autoscaler.kubernetes.io/scale-down-disabled"] = "true" } diff --git a/pkg/controllers/resources/persistentvolumeclaims/syncer.go b/pkg/controllers/resources/persistentvolumeclaims/syncer.go index e64bf534a..357cb7110 100644 --- a/pkg/controllers/resources/persistentvolumeclaims/syncer.go +++ b/pkg/controllers/resources/persistentvolumeclaims/syncer.go @@ -35,14 +35,14 @@ const ( ) func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { - storageClassesEnabled := ctx.Controllers.Has("storageclasses") + storageClassesEnabled := ctx.Config.Sync.ToHost.StorageClasses.Enabled excludedAnnotations := []string{bindCompletedAnnotation, boundByControllerAnnotation, storageProvisionerAnnotation} return &persistentVolumeClaimSyncer{ NamespacedTranslator: translator.NewNamespacedTranslator(ctx, "persistent-volume-claim", &corev1.PersistentVolumeClaim{}, excludedAnnotations...), storageClassesEnabled: storageClassesEnabled, - schedulerEnabled: ctx.Options.EnableScheduler, - useFakePersistentVolumes: !ctx.Controllers.Has("persistentvolumes"), + schedulerEnabled: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled, + useFakePersistentVolumes: !ctx.Config.Sync.ToHost.PersistentVolumes.Enabled, }, nil } diff --git a/pkg/controllers/resources/persistentvolumeclaims/syncer_test.go b/pkg/controllers/resources/persistentvolumeclaims/syncer_test.go index 3d8e3d5f8..8a8607002 100644 --- a/pkg/controllers/resources/persistentvolumeclaims/syncer_test.go +++ b/pkg/controllers/resources/persistentvolumeclaims/syncer_test.go @@ -132,7 +132,7 @@ func TestSync(t *testing.T) { generictesting.RunTestsWithContext(t, func(pClient *testingutil.FakeIndexClient, vClient *testingutil.FakeIndexClient) *synccontext.RegisterContext { ctx := generictesting.NewFakeRegisterContext(pClient, vClient) - ctx.Controllers.Delete("storageclasses") + ctx.Config.Sync.ToHost.StorageClasses.Enabled = false return ctx }, []*generictesting.SyncTest{ { diff --git a/pkg/controllers/resources/persistentvolumes/register.go b/pkg/controllers/resources/persistentvolumes/register.go index 5928fa1fb..1afdb4c64 100644 --- a/pkg/controllers/resources/persistentvolumes/register.go +++ b/pkg/controllers/resources/persistentvolumes/register.go @@ -6,7 +6,7 @@ import ( ) func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { - if !ctx.Controllers.Has("persistentvolumes") { + if !ctx.Config.Sync.ToHost.PersistentVolumes.Enabled { return NewFakeSyncer(ctx) } diff --git a/pkg/controllers/resources/pods/syncer.go b/pkg/controllers/resources/pods/syncer.go index 79ac2b884..838b5a717 100644 --- a/pkg/controllers/resources/pods/syncer.go +++ b/pkg/controllers/resources/pods/syncer.go @@ -49,21 +49,16 @@ func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { // parse node selector var nodeSelector *metav1.LabelSelector - if ctx.Options.EnforceNodeSelector && ctx.Options.NodeSelector != "" { - nodeSelector, err = metav1.ParseToLabelSelector(ctx.Options.NodeSelector) - if err != nil { - return nil, errors.Wrap(err, "parse node selector") - } else if len(nodeSelector.MatchExpressions) > 0 { - return nil, errors.New("match expressions in the node selector are not supported") - } else if len(nodeSelector.MatchLabels) == 0 { - return nil, errors.New("at least one label=value pair has to be defined in the label selector") + if len(ctx.Config.Sync.FromHost.Nodes.Selector.Labels) > 0 { + nodeSelector = &metav1.LabelSelector{ + MatchLabels: ctx.Config.Sync.FromHost.Nodes.Selector.Labels, } } // parse tolerations var tolerations []*corev1.Toleration - if len(ctx.Options.Tolerations) > 0 { - for _, t := range ctx.Options.Tolerations { + if len(ctx.Config.Sync.ToHost.Pods.EnforceTolerations) > 0 { + for _, t := range ctx.Config.Sync.ToHost.Pods.EnforceTolerations { tol, err := toleration.ParseToleration(t) if err == nil { tolerations = append(tolerations, &tol) @@ -83,8 +78,8 @@ func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { return &podSyncer{ NamespacedTranslator: namespacedTranslator, - serviceName: ctx.Options.ServiceName, - enableScheduler: ctx.Options.EnableScheduler, + serviceName: ctx.Config.ServiceName, + enableScheduler: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled, virtualClusterClient: virtualClusterClient, physicalClusterClient: physicalClusterClient, @@ -93,7 +88,7 @@ func New(ctx *synccontext.RegisterContext) (syncer.Object, error) { nodeSelector: nodeSelector, tolerations: tolerations, - podSecurityStandard: ctx.Options.EnforcePodSecurityStandard, + podSecurityStandard: ctx.Config.Policies.PodSecurityStandard, }, nil } diff --git a/pkg/controllers/resources/pods/syncer_test.go b/pkg/controllers/resources/pods/syncer_test.go index 65acb2f40..d15ca09ea 100644 --- a/pkg/controllers/resources/pods/syncer_test.go +++ b/pkg/controllers/resources/pods/syncer_test.go @@ -62,14 +62,14 @@ func TestSync(t *testing.T) { pVclusterService := corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: generictesting.DefaultTestVclusterServiceName, + Name: generictesting.DefaultTestVClusterServiceName, Namespace: generictesting.DefaultTestCurrentNamespace, }, Spec: corev1.ServiceSpec{ ClusterIP: "1.2.3.4", }, } - translate.VClusterName = generictesting.DefaultTestVclusterName + translate.VClusterName = generictesting.DefaultTestVClusterName pDNSService := corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: translate.Default.PhysicalName("kube-dns", "kube-system"), @@ -137,7 +137,10 @@ func TestSync(t *testing.T) { }, }, } - nodeSelectorOption := "labelB=enforcedB,otherLabel=abc" + nodeSelectorOption := map[string]string{ + "labelB": "enforcedB", + "otherLabel": "abc", + } pPodWithNodeSelector := pPodBase.DeepCopy() pPodWithNodeSelector.Spec.NodeSelector = map[string]string{ "labelA": "valueA", @@ -227,7 +230,7 @@ func TestSync(t *testing.T) { }, } - vHostPath := fmt.Sprintf(podtranslate.VirtualPathTemplate, generictesting.DefaultTestCurrentNamespace, generictesting.DefaultTestVclusterName) + vHostPath := fmt.Sprintf(podtranslate.VirtualPathTemplate, generictesting.DefaultTestCurrentNamespace, generictesting.DefaultTestVClusterName) hostToContainer := corev1.MountPropagationHostToContainer pHostPathPod := &corev1.Pod{ @@ -451,8 +454,7 @@ func TestSync(t *testing.T) { }, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforceNodeSelector = true - ctx.Options.NodeSelector = nodeSelectorOption + ctx.Config.Sync.FromHost.Nodes.Selector.Labels = nodeSelectorOption syncCtx, syncer := generictesting.FakeStartSyncer(t, ctx, New) _, err := syncer.(*podSyncer).SyncToHost(syncCtx, vPodWithNodeSelector.DeepCopy()) assert.NilError(t, err) @@ -485,7 +487,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {pPodPss.DeepCopy()}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforcePodSecurityStandard = string(api.LevelPrivileged) + ctx.Config.Policies.PodSecurityStandard = string(api.LevelPrivileged) syncCtx, syncer := generictesting.FakeStartSyncer(t, ctx, New) _, err := syncer.(*podSyncer).SyncToHost(syncCtx, vPodPSS.DeepCopy()) assert.NilError(t, err) @@ -502,7 +504,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.EnforcePodSecurityStandard = string(api.LevelRestricted) + ctx.Config.Policies.PodSecurityStandard = string(api.LevelRestricted) syncCtx, syncer := generictesting.FakeStartSyncer(t, ctx, New) _, err := syncer.(*podSyncer).SyncToHost(syncCtx, vPodPSSR.DeepCopy()) assert.NilError(t, err) @@ -519,7 +521,7 @@ func TestSync(t *testing.T) { corev1.SchemeGroupVersion.WithKind("Pod"): {pHostPathPod.DeepCopy()}, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.MountPhysicalHostPaths = true + ctx.Config.ControlPlane.HostPathMapper.Enabled = true synccontext, syncer := generictesting.FakeStartSyncer(t, ctx, New) _, err := syncer.(*podSyncer).SyncToHost(synccontext, vHostPathPod.DeepCopy()) assert.NilError(t, err) @@ -551,7 +553,7 @@ func TestSync(t *testing.T) { }, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.SyncLabels = []string{syncLabelsWildcard} + ctx.Config.Experimental.SyncSettings.SyncLabels = []string{syncLabelsWildcard} syncCtx, syncer := generictesting.FakeStartSyncer(t, ctx, New) _, err := syncer.(*podSyncer).SyncToHost(syncCtx, vPodWithLabels.DeepCopy()) assert.NilError(t, err) diff --git a/pkg/controllers/resources/pods/translate/hostpath.go b/pkg/controllers/resources/pods/translate/hostpath.go index 46401e0b3..d39d35161 100644 --- a/pkg/controllers/resources/pods/translate/hostpath.go +++ b/pkg/controllers/resources/pods/translate/hostpath.go @@ -128,9 +128,7 @@ func (t *translator) rewriteHostPaths(pPod *corev1.Pod) { } } - if t.hostpathMountPropagation { - t.ensureMountPropagation(pPod) - } + t.ensureMountPropagation(pPod) } } diff --git a/pkg/controllers/resources/pods/translate/image_translator.go b/pkg/controllers/resources/pods/translate/image_translator.go index 6b25d4246..3d59fd96a 100644 --- a/pkg/controllers/resources/pods/translate/image_translator.go +++ b/pkg/controllers/resources/pods/translate/image_translator.go @@ -1,10 +1,5 @@ package translate -import ( - "fmt" - "strings" -) - type ImageTranslator interface { Translate(image string) string } @@ -13,19 +8,9 @@ type imageTranslator struct { translateImages map[string]string } -func NewImageTranslator(translateImages []string) (ImageTranslator, error) { - translateImagesMap := map[string]string{} - for _, t := range translateImages { - i := strings.Split(strings.TrimSpace(t), "=") - if len(i) != 2 { - return nil, fmt.Errorf("error parsing translate image '%s': bad format expected image1=image2", t) - } - - translateImagesMap[i[0]] = i[1] - } - +func NewImageTranslator(translateImages map[string]string) (ImageTranslator, error) { return &imageTranslator{ - translateImages: translateImagesMap, + translateImages: translateImages, }, nil } diff --git a/pkg/controllers/resources/pods/translate/translator.go b/pkg/controllers/resources/pods/translate/translator.go index 1c3e8b43b..5dc505562 100644 --- a/pkg/controllers/resources/pods/translate/translator.go +++ b/pkg/controllers/resources/pods/translate/translator.go @@ -58,16 +58,12 @@ type Translator interface { } func NewTranslator(ctx *synccontext.RegisterContext, eventRecorder record.EventRecorder) (Translator, error) { - imageTranslator, err := NewImageTranslator(ctx.Options.TranslateImages) + imageTranslator, err := NewImageTranslator(ctx.Config.Sync.ToHost.Pods.TranslateImage) if err != nil { return nil, err } - name := ctx.Options.Name - if name == "" { - name = ctx.Options.ServiceName - } - + name := ctx.Config.Name virtualPath := fmt.Sprintf(VirtualPathTemplate, ctx.CurrentNamespace, name) virtualLogsPath := path.Join(virtualPath, "log") virtualKubeletPath := path.Join(virtualPath, "kubelet") @@ -81,23 +77,23 @@ func NewTranslator(ctx *synccontext.RegisterContext, eventRecorder record.EventR eventRecorder: eventRecorder, log: loghelper.New("pods-syncer-translator"), - defaultImageRegistry: ctx.Options.DefaultImageRegistry, - - serviceAccountSecretsEnabled: ctx.Options.ServiceAccountTokenSecrets, - clusterDomain: ctx.Options.ClusterDomain, - serviceAccount: ctx.Options.ServiceAccount, - overrideHosts: ctx.Options.OverrideHosts, - overrideHostsImage: ctx.Options.OverrideHostsContainerImage, - serviceAccountsEnabled: ctx.Controllers.Has("serviceaccounts"), - priorityClassesEnabled: ctx.Controllers.Has("priorityclasses"), - enableScheduler: ctx.Options.EnableScheduler, - syncedLabels: ctx.Options.SyncLabels, - - mountPhysicalHostPaths: ctx.Options.MountPhysicalHostPaths, - hostpathMountPropagation: true, - virtualLogsPath: virtualLogsPath, - virtualPodLogsPath: filepath.Join(virtualLogsPath, "pods"), - virtualKubeletPodPath: filepath.Join(virtualKubeletPath, "pods"), + defaultImageRegistry: ctx.Config.ControlPlane.Advanced.DefaultImageRegistry, + + serviceAccountSecretsEnabled: ctx.Config.Sync.ToHost.Pods.UseSecretsForSATokens, + clusterDomain: ctx.Config.Networking.Advanced.ClusterDomain, + serviceAccount: ctx.Config.ControlPlane.Advanced.WorkloadServiceAccount.Name, + overrideHosts: ctx.Config.Sync.ToHost.Pods.RewriteHosts.Enabled, + overrideHostsImage: ctx.Config.Sync.ToHost.Pods.RewriteHosts.InitContainerImage, + serviceAccountsEnabled: ctx.Config.Sync.ToHost.ServiceAccounts.Enabled, + priorityClassesEnabled: ctx.Config.Sync.ToHost.PriorityClasses.Enabled, + enableScheduler: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled, + syncedLabels: ctx.Config.Experimental.SyncSettings.SyncLabels, + + mountPhysicalHostPaths: ctx.Config.ControlPlane.HostPathMapper.Enabled && !ctx.Config.ControlPlane.HostPathMapper.Central, + + virtualLogsPath: virtualLogsPath, + virtualPodLogsPath: filepath.Join(virtualLogsPath, "pods"), + virtualKubeletPodPath: filepath.Join(virtualKubeletPath, "pods"), }, nil } @@ -111,6 +107,9 @@ type translator struct { defaultImageRegistry string + // this is needed for host path mapper (legacy) + mountPhysicalHostPaths bool + serviceAccountsEnabled bool serviceAccountSecretsEnabled bool clusterDomain string @@ -121,11 +120,9 @@ type translator struct { enableScheduler bool syncedLabels []string - mountPhysicalHostPaths bool - hostpathMountPropagation bool - virtualLogsPath string - virtualPodLogsPath string - virtualKubeletPodPath string + virtualLogsPath string + virtualPodLogsPath string + virtualKubeletPodPath string } func (t *translator) Translate(ctx context.Context, vPod *corev1.Pod, services []*corev1.Service, dnsIP string, kubeIP string) (*corev1.Pod, error) { @@ -428,9 +425,7 @@ func (t *translator) translateVolumes(ctx context.Context, pPod *corev1.Pod, vPo } // rewrite host paths if enabled - if t.mountPhysicalHostPaths || t.hostpathMountPropagation { - t.rewriteHostPaths(pPod) - } + t.rewriteHostPaths(pPod) return nil } diff --git a/pkg/controllers/resources/secrets/syncer.go b/pkg/controllers/resources/secrets/syncer.go index 2c8bdf574..b36ff2442 100644 --- a/pkg/controllers/resources/secrets/syncer.go +++ b/pkg/controllers/resources/secrets/syncer.go @@ -41,9 +41,9 @@ func NewSyncer(ctx *synccontext.RegisterContext, useLegacy bool) (syncer.Object, NamespacedTranslator: translator.NewNamespacedTranslator(ctx, "secret", &corev1.Secret{}), useLegacyIngress: useLegacy, - includeIngresses: ctx.Controllers.Has("ingresses"), + includeIngresses: ctx.Config.Sync.ToHost.Ingresses.Enabled, - syncAllSecrets: ctx.Options.SyncAllSecrets, + syncAllSecrets: ctx.Config.Sync.ToHost.Secrets.All, }, nil } @@ -59,7 +59,7 @@ type secretSyncer struct { var _ syncer.IndicesRegisterer = &secretSyncer{} func (s *secretSyncer) RegisterIndices(ctx *synccontext.RegisterContext) error { - if ctx.Controllers.Has("ingresses") { + if ctx.Config.Sync.ToHost.Ingresses.Enabled { if s.useLegacyIngress { err := ctx.VirtualManager.GetFieldIndexer().IndexField(ctx.Context, &networkingv1beta1.Ingress{}, constants.IndexByIngressSecret, func(rawObj client.Object) []string { return legacy.SecretNamesFromIngress(rawObj.(*networkingv1beta1.Ingress)) diff --git a/pkg/controllers/resources/secrets/syncer_test.go b/pkg/controllers/resources/secrets/syncer_test.go index 26726475d..6381a3cf0 100644 --- a/pkg/controllers/resources/secrets/syncer_test.go +++ b/pkg/controllers/resources/secrets/syncer_test.go @@ -107,7 +107,7 @@ func TestSync(t *testing.T) { }, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.SyncLabels = []string{testLabel} + ctx.Config.Experimental.SyncSettings.SyncLabels = []string{testLabel} syncContext, syncer := newFakeSyncer(t, ctx) _, err := syncer.(*secretSyncer).SyncToHost(syncContext, baseSecret) assert.NilError(t, err) @@ -128,7 +128,7 @@ func TestSync(t *testing.T) { }, }, Sync: func(ctx *synccontext.RegisterContext) { - ctx.Options.SyncLabels = []string{testLabel} + ctx.Config.Experimental.SyncSettings.SyncLabels = []string{testLabel} syncContext, syncer := newFakeSyncer(t, ctx) _, err := syncer.(*secretSyncer).Sync(syncContext, syncedSecret, updatedSecret) assert.NilError(t, err) diff --git a/pkg/controllers/resources/services/syncer.go b/pkg/controllers/resources/services/syncer.go index e01481968..26d92bc60 100644 --- a/pkg/controllers/resources/services/syncer.go +++ b/pkg/controllers/resources/services/syncer.go @@ -28,7 +28,7 @@ func New(ctx *synccontext.RegisterContext) (syncertypes.Object, error) { // overriding it, which would cause endless updates back and forth. NamespacedTranslator: translator.NewNamespacedTranslator(ctx, "service", &corev1.Service{}, "field.cattle.io/publicEndpoints"), - serviceName: ctx.Options.ServiceName, + serviceName: ctx.Config.ServiceName, }, nil } diff --git a/pkg/controllers/syncer/context/context.go b/pkg/controllers/syncer/context/context.go index 58749c70a..ed691bda5 100644 --- a/pkg/controllers/syncer/context/context.go +++ b/pkg/controllers/syncer/context/context.go @@ -3,9 +3,8 @@ package context import ( "context" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/loghelper" - "k8s.io/apimachinery/pkg/util/sets" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -24,8 +23,7 @@ type SyncContext struct { type RegisterContext struct { Context context.Context - Options *options.VirtualClusterOptions - Controllers sets.Set[string] + Config *config.VirtualClusterConfig CurrentNamespace string CurrentNamespaceClient client.Client diff --git a/pkg/controllers/syncer/testing/context.go b/pkg/controllers/syncer/testing/context.go index 5c626ec1f..1ab0afc23 100644 --- a/pkg/controllers/syncer/testing/context.go +++ b/pkg/controllers/syncer/testing/context.go @@ -2,10 +2,11 @@ package testing import ( "context" + "os" + "path/filepath" "testing" - "github.com/loft-sh/vcluster/pkg/constants" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/translate" "github.com/loft-sh/vcluster/pkg/util/log" @@ -25,8 +26,8 @@ import ( const ( DefaultTestTargetNamespace = "test" DefaultTestCurrentNamespace = "vcluster" - DefaultTestVclusterName = "vcluster" - DefaultTestVclusterServiceName = "vcluster" + DefaultTestVClusterName = "vcluster" + DefaultTestVClusterServiceName = "vcluster" ) func FakeStartSyncer(t *testing.T, ctx *synccontext.RegisterContext, create func(ctx *synccontext.RegisterContext) (syncer.Object, error)) (*synccontext.SyncContext, syncer.Object) { @@ -48,13 +49,8 @@ func FakeStartSyncer(t *testing.T, ctx *synccontext.RegisterContext, create func func NewFakeRegisterContext(pClient *testingutil.FakeIndexClient, vClient *testingutil.FakeIndexClient) *synccontext.RegisterContext { translate.Default = translate.NewSingleNamespaceTranslator(DefaultTestTargetNamespace) return &synccontext.RegisterContext{ - Context: context.Background(), - Options: &options.VirtualClusterOptions{ - Name: DefaultTestVclusterName, - ServiceName: DefaultTestVclusterServiceName, - TargetNamespace: DefaultTestTargetNamespace, - }, - Controllers: constants.ExistingControllers.Clone(), + Context: context.Background(), + Config: NewFakeConfig(), CurrentNamespace: DefaultTestCurrentNamespace, CurrentNamespaceClient: pClient, VirtualManager: newFakeManager(vClient), @@ -62,6 +58,39 @@ func NewFakeRegisterContext(pClient *testingutil.FakeIndexClient, vClient *testi } } +func NewFakeConfig() *config.VirtualClusterConfig { + // find vCluster config + workDir, err := os.Getwd() + if err != nil { + panic("current workDir: " + err.Error()) + } + + // find base dir + configPath := "" + for { + configPath = filepath.Join(workDir, "chart", "values.yaml") + _, err = os.Stat(configPath) + if err == nil { + break + } else if workDir == "/" { + panic("couldn't find chart/values.yaml") + } + + workDir = filepath.Dir(workDir) + } + + // parse config + vConfig, err := config.ParseConfig(configPath, DefaultTestVClusterName, nil) + if err != nil { + panic("load test config: " + workDir + " - " + err.Error()) + } + + vConfig.Name = DefaultTestVClusterName + vConfig.ServiceName = DefaultTestVClusterServiceName + vConfig.TargetNamespace = DefaultTestTargetNamespace + return vConfig +} + type fakeEventBroadcaster struct{} func (f *fakeEventBroadcaster) StartEventWatcher(_ func(*corev1.Event)) watch.Interface { diff --git a/pkg/controllers/syncer/translator/cluster_translator.go b/pkg/controllers/syncer/translator/cluster_translator.go index 191e3049c..a6cfe006e 100644 --- a/pkg/controllers/syncer/translator/cluster_translator.go +++ b/pkg/controllers/syncer/translator/cluster_translator.go @@ -19,7 +19,7 @@ func NewClusterTranslator(ctx *context.RegisterContext, name string, obj client. virtualClient: ctx.VirtualManager.GetClient(), obj: obj, nameTranslator: nameTranslator, - syncedLabels: ctx.Options.SyncLabels, + syncedLabels: ctx.Config.Experimental.SyncSettings.SyncLabels, } } diff --git a/pkg/controllers/syncer/translator/namespaced_translator.go b/pkg/controllers/syncer/translator/namespaced_translator.go index 936ee777b..de6d817d4 100644 --- a/pkg/controllers/syncer/translator/namespaced_translator.go +++ b/pkg/controllers/syncer/translator/namespaced_translator.go @@ -21,7 +21,7 @@ func NewNamespacedTranslator(ctx *context.RegisterContext, name string, obj clie return &namespacedTranslator{ name: name, - syncedLabels: ctx.Options.SyncLabels, + syncedLabels: ctx.Config.Experimental.SyncSettings.SyncLabels, excludedAnnotations: excludedAnnotations, virtualClient: ctx.VirtualManager.GetClient(), diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 3e097791e..35ac06611 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -36,6 +36,7 @@ type UpgradeOptions struct { Insecure bool Atomic bool Force bool + Debug bool } const ( @@ -166,6 +167,9 @@ func (c *client) run(ctx context.Context, name, namespace string, options Upgrad if options.Atomic { args = append(args, "--atomic") } + if options.Debug { + args = append(args, "--debug") + } return c.execute(ctx, args, command, options.WorkDir) } diff --git a/pkg/k0s/k0s.go b/pkg/k0s/k0s.go index 3847c0640..abb8d0782 100644 --- a/pkg/k0s/k0s.go +++ b/pkg/k0s/k0s.go @@ -1,30 +1,69 @@ package k0s import ( + "bytes" "context" + "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" + "text/template" - "github.com/ghodss/yaml" + vclusterconfig "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/commandwriter" "k8s.io/klog/v2" ) -const ( - VClusterCommandEnv = "VCLUSTER_COMMAND" -) - -type k0sCommand struct { - Command []string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` -} - const runDir = "/run/k0s" +const cidrPlaceholder = "CIDR_PLACEHOLDER" + +var k0sConfig = `apiVersion: k0s.k0sproject.io/v1beta1 +kind: Cluster +metadata: + name: k0s +spec: + api: + port: 6443 + k0sApiPort: 9443 + extraArgs: + bind-address: 127.0.0.1 + enable-admission-plugins: NodeRestriction + endpoint-reconciler-type: none + network: + {{- if .Values.serviceCIDR }} + serviceCIDR: {{ .Values.serviceCIDR }} + {{- else }} + # Will be replaced automatically by the syncer container on first startup + serviceCIDR: CIDR_PLACEHOLDER + {{- end }} + provider: custom + {{- if .Values.networking.advanced.clusterDomain }} + clusterDomain: {{ .Values.networking.advanced.clusterDomain }} + {{- end}} + controllerManager: + extraArgs: + {{- if not .Values.controlPlane.advanced.virtualScheduler.enabled }} + controllers: '*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' + {{- else }} + controllers: '*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl' + node-monitor-grace-period: 1h + node-monitor-period: 1h + {{- end }} + {{- if .Values.controlPlane.backingStore.embeddedEtcd.enabled }} + storage: + etcd: + externalCluster: + endpoints: ["127.0.0.1:2379"] + caFile: /data/k0s/pki/etcd/ca.crt + etcdPrefix: "/registry" + clientCertFile: /data/k0s/pki/apiserver-etcd-client.crt + clientKeyFile: /data/k0s/pki/apiserver-etcd-client.key + {{- end }}` -func StartK0S(ctx context.Context, cancel context.CancelFunc) error { +func StartK0S(ctx context.Context, cancel context.CancelFunc, vConfig *config.VirtualClusterConfig) error { // this is not really useful but go isn't happy if we don't cancel the context // everywhere defer cancel() @@ -35,14 +74,25 @@ func StartK0S(ctx context.Context, cancel context.CancelFunc) error { _ = os.RemoveAll(filepath.Join(runDir, entry.Name())) } - // create command - command := &k0sCommand{} - err := yaml.Unmarshal([]byte(os.Getenv(VClusterCommandEnv)), command) - if err != nil { - return fmt.Errorf("parsing k0s command %s: %w", os.Getenv(VClusterCommandEnv), err) + // build args + args := []string{} + if len(vConfig.ControlPlane.Distro.K0S.Command) > 0 { + args = append(args, vConfig.ControlPlane.Distro.K0S.Command...) + } else { + args = append(args, "/binaries/k0s") + args = append(args, "controller") + args = append(args, "--config=/tmp/k0s-config.yaml") + args = append(args, "--data-dir=/data/k0s") + args = append(args, "--status-socket=/run/k0s/status.sock") + if vConfig.ControlPlane.Advanced.VirtualScheduler.Enabled { + args = append(args, "--disable-components=konnectivity-server,csr-approver,kube-proxy,coredns,network-provider,helm,metrics-server,worker-config") + } else { + args = append(args, "--disable-components=konnectivity-server,kube-scheduler,csr-approver,kube-proxy,coredns,network-provider,helm,metrics-server,worker-config") + } } - args := append(command.Command, command.Args...) + // add extra args + args = append(args, vConfig.ControlPlane.Distro.K0S.ExtraArgs...) // check what writer we should use writer, err := commandwriter.NewCommandWriter("k0s") @@ -56,6 +106,7 @@ func StartK0S(ctx context.Context, cancel context.CancelFunc) error { cmd := exec.CommandContext(ctx, args[0], args[1:]...) cmd.Stdout = writer.Writer() cmd.Stderr = writer.Writer() + cmd.Env = append(os.Environ(), "ETCD_UNSUPPORTED_ARCH=arm64") err = cmd.Run() // make sure we wait for scanner to be done @@ -67,3 +118,64 @@ func StartK0S(ctx context.Context, cancel context.CancelFunc) error { } return nil } + +func WriteK0sConfig( + serviceCIDR string, + vConfig *config.VirtualClusterConfig, +) error { + // choose config + configTemplate := k0sConfig + if vConfig.Config.ControlPlane.Distro.K0S.Config != "" { + configTemplate = vConfig.Config.ControlPlane.Distro.K0S.Config + } + + // exec template + outBytes, err := ExecTemplate(configTemplate, vConfig.Name, "", &vConfig.Config) + if err != nil { + return fmt.Errorf("exec k0s config template: %w", err) + } + + // apply changes + updatedConfig := []byte(strings.ReplaceAll(string(outBytes), cidrPlaceholder, serviceCIDR)) + + // write the config to file + err = os.WriteFile("/tmp/k0s-config.yaml", updatedConfig, 0640) + if err != nil { + klog.Errorf("error while write k0s config to file: %s", err.Error()) + return err + } + + return nil +} + +func ExecTemplate(templateContents string, name, namespace string, values *vclusterconfig.Config) ([]byte, error) { + out, err := json.Marshal(values) + if err != nil { + return nil, err + } + + rawValues := map[string]interface{}{} + err = json.Unmarshal(out, &rawValues) + if err != nil { + return nil, err + } + + t, err := template.New("").Parse(templateContents) + if err != nil { + return nil, err + } + + b := &bytes.Buffer{} + err = t.Execute(b, map[string]interface{}{ + "Values": rawValues, + "Release": map[string]interface{}{ + "Name": name, + "Namespace": namespace, + }, + }) + if err != nil { + return nil, err + } + + return b.Bytes(), nil +} diff --git a/pkg/k3s/k3s.go b/pkg/k3s/k3s.go index ac4ba3549..31e3f6f8d 100644 --- a/pkg/k3s/k3s.go +++ b/pkg/k3s/k3s.go @@ -7,7 +7,7 @@ import ( "os/exec" "strings" - "github.com/ghodss/yaml" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/commandwriter" "github.com/loft-sh/vcluster/pkg/util/random" corev1 "k8s.io/api/core/v1" @@ -19,27 +19,45 @@ import ( var tokenPath = "/data/server/token" -const VClusterCommandEnv = "VCLUSTER_COMMAND" - -type k3sCommand struct { - Command []string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` -} - -func StartK3S(ctx context.Context, serviceCIDR, k3sToken string) error { - command := &k3sCommand{} - err := yaml.Unmarshal([]byte(os.Getenv(VClusterCommandEnv)), command) - if err != nil { - return fmt.Errorf("parsing k3s command %s: %w", os.Getenv(VClusterCommandEnv), err) +func StartK3S(ctx context.Context, vConfig *config.VirtualClusterConfig, serviceCIDR, k3sToken string) error { + // build args + args := []string{} + if len(vConfig.ControlPlane.Distro.K3S.Command) > 0 { + args = append(args, vConfig.ControlPlane.Distro.K3S.Command...) + } else { + args = append(args, "/binaries/k3s") + args = append(args, "server") + args = append(args, "--write-kubeconfig=/data/k3s-config/kube-config.yaml") + args = append(args, "--data-dir=/data") + args = append(args, "--service-cidr="+serviceCIDR) + args = append(args, "--token="+strings.TrimSpace(k3sToken)) + args = append(args, "--disable=traefik,servicelb,metrics-server,local-storage,coredns") + args = append(args, "--disable-network-policy") + args = append(args, "--disable-agent") + args = append(args, "--disable-cloud-controller") + args = append(args, "--egress-selector-mode=disabled") + args = append(args, "--flannel-backend=none") + args = append(args, "--kube-apiserver-arg=bind-address=127.0.0.1") + if vConfig.ControlPlane.Advanced.VirtualScheduler.Enabled { + args = append(args, "--kube-controller-manager-arg=controllers=*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl") + args = append(args, "--kube-apiserver-arg=endpoint-reconciler-type=none") + args = append(args, "--kube-controller-manager-arg=node-monitor-grace-period=1h") + args = append(args, "--kube-controller-manager-arg=node-monitor-period=1h") + } else { + args = append(args, "--disable-scheduler") + args = append(args, "--kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl") + args = append(args, "--kube-apiserver-arg=endpoint-reconciler-type=none") + } + if vConfig.ControlPlane.BackingStore.EmbeddedEtcd.Enabled { + args = append(args, "--datastore-endpoint=https://localhost:2379") + args = append(args, "--datastore-cafile=/data/pki/etcd/ca.crt") + args = append(args, "--datastore-certfile=/data/pki/apiserver-etcd-client.crt") + args = append(args, "--datastore-keyfile=/data/pki/apiserver-etcd-client.key") + } } - // add service cidr and k3s token - command.Args = append( - command.Args, - "--service-cidr", serviceCIDR, - "--token", strings.TrimSpace(k3sToken), - ) - args := append(command.Command, command.Args...) + // add extra args + args = append(args, vConfig.ControlPlane.Distro.K3S.ExtraArgs...) // check what writer we should use writer, err := commandwriter.NewCommandWriter("k3s") @@ -65,7 +83,12 @@ func StartK3S(ctx context.Context, serviceCIDR, k3sToken string) error { return nil } -func EnsureK3SToken(ctx context.Context, currentNamespaceClient kubernetes.Interface, currentNamespace, vClusterName string) (string, error) { +func EnsureK3SToken(ctx context.Context, currentNamespaceClient kubernetes.Interface, currentNamespace, vClusterName string, options *config.VirtualClusterConfig) (string, error) { + // check if token is set externally + if options.ControlPlane.Distro.K3S.Token != "" { + return options.ControlPlane.Distro.K3S.Token, nil + } + // check if secret exists secretName := fmt.Sprintf("vc-k3s-%s", vClusterName) secret, err := currentNamespaceClient.CoreV1().Secrets(currentNamespace).Get(ctx, secretName, metav1.GetOptions{}) @@ -92,7 +115,6 @@ func EnsureK3SToken(ctx context.Context, currentNamespaceClient kubernetes.Inter }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) - if kerrors.IsAlreadyExists(err) { // retrieve k3s secret again secret, err = currentNamespaceClient.CoreV1().Secrets(currentNamespace).Get(ctx, secretName, metav1.GetOptions{}) diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go index a69a360d9..caa71771f 100644 --- a/pkg/k8s/k8s.go +++ b/pkg/k8s/k8s.go @@ -6,43 +6,75 @@ import ( "errors" "fmt" "net/http" - "os" "os/exec" "strings" "time" - "github.com/ghodss/yaml" + vclusterconfig "github.com/loft-sh/vcluster/config" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/etcd" "github.com/loft-sh/vcluster/pkg/util/commandwriter" "golang.org/x/sync/errgroup" "k8s.io/klog/v2" ) -const apiServerCmd = "APISERVER_COMMAND" -const schedulerCmd = "SCHEDULER_COMMAND" -const controllerCmd = "CONTROLLER_COMMAND" - -type command struct { - Command []string `json:"command,omitempty"` -} - -func StartK8S(ctx context.Context, serviceCIDR string) error { +func StartK8S( + ctx context.Context, + serviceCIDR string, + apiServer vclusterconfig.DistroContainerDisabled, + controllerManager vclusterconfig.DistroContainerDisabled, + scheduler vclusterconfig.DistroContainer, + vConfig *config.VirtualClusterConfig, +) error { serviceCIDRArg := fmt.Sprintf("--service-cluster-ip-range=%s", serviceCIDR) eg := &errgroup.Group{} // start api server first - apiEnv, ok := os.LookupEnv(apiServerCmd) - if ok { - apiCommand := &command{} - err := yaml.Unmarshal([]byte(apiEnv), apiCommand) - if err != nil { - return fmt.Errorf("parsing apiserver command %s: %w", apiEnv, err) - } - - apiCommand.Command = append(apiCommand.Command, serviceCIDRArg) + if !apiServer.Disabled { eg.Go(func() error { + // build flags + args := []string{} + if len(apiServer.Command) > 0 { + args = append(args, apiServer.Command...) + } else { + args = append(args, "/binaries/kube-apiserver") + args = append(args, "--advertise-address=127.0.0.1") + args = append(args, serviceCIDRArg) + args = append(args, "--bind-address=127.0.0.1") + args = append(args, "--allow-privileged=true") + args = append(args, "--authorization-mode=RBAC") + args = append(args, "--client-ca-file="+vConfig.VirtualClusterKubeConfig().ClientCACert) + args = append(args, "--enable-bootstrap-token-auth=true") + args = append(args, "--etcd-cafile=/pki/etcd/ca.crt") + args = append(args, "--etcd-certfile=/pki/apiserver-etcd-client.crt") + args = append(args, "--etcd-keyfile=/pki/apiserver-etcd-client.key") + if vConfig.ControlPlane.BackingStore.EmbeddedEtcd.Enabled { + args = append(args, "--etcd-servers=https://127.0.0.1:2379") + } else { + args = append(args, "--etcd-servers=https://"+vConfig.Name+"-etcd:2379") + } + args = append(args, "--proxy-client-cert-file=/pki/front-proxy-client.crt") + args = append(args, "--proxy-client-key-file=/pki/front-proxy-client.key") + args = append(args, "--requestheader-allowed-names=front-proxy-client") + args = append(args, "--requestheader-client-ca-file=/pki/front-proxy-ca.crt") + args = append(args, "--requestheader-extra-headers-prefix=X-Remote-Extra-") + args = append(args, "--requestheader-group-headers=X-Remote-Group") + args = append(args, "--requestheader-username-headers=X-Remote-User") + args = append(args, "--secure-port=6443") + args = append(args, "--service-account-issuer=https://kubernetes.default.svc.cluster.local") + args = append(args, "--service-account-key-file=/pki/sa.pub") + args = append(args, "--service-account-signing-key-file=/pki/sa.key") + args = append(args, "--tls-cert-file=/pki/apiserver.crt") + args = append(args, "--tls-private-key-file=/pki/apiserver.key") + args = append(args, "--watch-cache=false") + args = append(args, "--endpoint-reconciler-type=none") + } + + // add extra args + args = append(args, apiServer.ExtraArgs...) + // get etcd endpoints and certificates from flags - endpoints, certificates, err := etcd.EndpointsAndCertificatesFromFlags(apiCommand.Command) + endpoints, certificates, err := etcd.EndpointsAndCertificatesFromFlags(args) if err != nil { return fmt.Errorf("get etcd certificates and endpoint: %w", err) } @@ -54,7 +86,7 @@ func StartK8S(ctx context.Context, serviceCIDR string) error { } // now start the api server - return RunCommand(ctx, apiCommand, "apiserver") + return RunCommand(ctx, args, "apiserver") }) } @@ -65,31 +97,74 @@ func StartK8S(ctx context.Context, serviceCIDR string) error { } // start controller command - controllerEnv, ok := os.LookupEnv(controllerCmd) - if ok { - controllerCommand := &command{} - err := yaml.Unmarshal([]byte(controllerEnv), controllerCommand) - if err != nil { - return fmt.Errorf("parsing controller command %s: %w", controllerEnv, err) - } - - controllerCommand.Command = append(controllerCommand.Command, serviceCIDRArg) + if !controllerManager.Disabled { eg.Go(func() error { - return RunCommand(ctx, controllerCommand, "controller") + // build flags + args := []string{} + if len(controllerManager.Command) > 0 { + args = append(args, controllerManager.Command...) + } else { + args = append(args, "/binaries/kube-controller-manager") + args = append(args, serviceCIDRArg) + args = append(args, "--authentication-kubeconfig=/pki/controller-manager.conf") + args = append(args, "--authorization-kubeconfig=/pki/controller-manager.conf") + args = append(args, "--bind-address=127.0.0.1") + args = append(args, "--client-ca-file=/pki/ca.crt") + args = append(args, "--cluster-name=kubernetes") + args = append(args, "--cluster-signing-cert-file=/pki/ca.crt") + args = append(args, "--cluster-signing-key-file=/pki/ca.key") + args = append(args, "--horizontal-pod-autoscaler-sync-period=60s") + args = append(args, "--kubeconfig=/pki/controller-manager.conf") + args = append(args, "--node-monitor-grace-period=180s") + args = append(args, "--node-monitor-period=30s") + args = append(args, "--pvclaimbinder-sync-period=60s") + args = append(args, "--requestheader-client-ca-file=/pki/front-proxy-ca.crt") + args = append(args, "--root-ca-file=/pki/ca.crt") + args = append(args, "--service-account-private-key-file=/pki/sa.key") + args = append(args, "--use-service-account-credentials=true") + if vConfig.ControlPlane.StatefulSet.HighAvailability.Replicas > 1 { + args = append(args, "--leader-elect=true") + } else { + args = append(args, "--leader-elect=false") + } + if vConfig.ControlPlane.Advanced.VirtualScheduler.Enabled { + args = append(args, "--controllers=*,-nodeipam,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl") + args = append(args, "--node-monitor-grace-period=1h") + args = append(args, "--node-monitor-period=1h") + } else { + args = append(args, "--controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle,-ttl") + } + } + + // add extra args + args = append(args, controllerManager.ExtraArgs...) + return RunCommand(ctx, args, "controller-manager") }) } // start scheduler command - schedulerEnv, ok := os.LookupEnv(schedulerCmd) - if ok { - schedulerCommand := &command{} - err := yaml.Unmarshal([]byte(schedulerEnv), schedulerCommand) - if err != nil { - return fmt.Errorf("parsing scheduler command %s: %w", schedulerEnv, err) - } - + if vConfig.ControlPlane.Advanced.VirtualScheduler.Enabled { eg.Go(func() error { - return RunCommand(ctx, schedulerCommand, "scheduler") + // build flags + args := []string{} + if len(scheduler.Command) > 0 { + args = append(args, scheduler.Command...) + } else { + args = append(args, "/binaries/kube-scheduler") + args = append(args, "--authentication-kubeconfig=/pki/scheduler.conf") + args = append(args, "--authorization-kubeconfig=/pki/scheduler.conf") + args = append(args, "--bind-address=127.0.0.1") + args = append(args, "--kubeconfig=/pki/scheduler.conf") + if vConfig.ControlPlane.StatefulSet.HighAvailability.Replicas > 1 { + args = append(args, "--leader-elect=true") + } else { + args = append(args, "--leader-elect=false") + } + } + + // add extra args + args = append(args, scheduler.ExtraArgs...) + return RunCommand(ctx, args, "scheduler") }) } @@ -103,7 +178,7 @@ func StartK8S(ctx context.Context, serviceCIDR string) error { return err } -func RunCommand(ctx context.Context, command *command, component string) error { +func RunCommand(ctx context.Context, command []string, component string) error { writer, err := commandwriter.NewCommandWriter(component) if err != nil { return err @@ -111,8 +186,8 @@ func RunCommand(ctx context.Context, command *command, component string) error { defer writer.Writer() // start the command - klog.InfoS("Starting "+component, "args", strings.Join(command.Command, " ")) - cmd := exec.CommandContext(ctx, command.Command[0], command.Command[1:]...) + klog.InfoS("Starting "+component, "args", strings.Join(command, " ")) + cmd := exec.CommandContext(ctx, command[0], command[1:]...) cmd.Stdout = writer.Writer() cmd.Stderr = writer.Writer() err = cmd.Run() diff --git a/pkg/leaderelection/leaderelection.go b/pkg/leaderelection/leaderelection.go index 97878c050..6b515ee93 100644 --- a/pkg/leaderelection/leaderelection.go +++ b/pkg/leaderelection/leaderelection.go @@ -6,7 +6,7 @@ import ( "os" "time" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/telemetry" "github.com/loft-sh/vcluster/pkg/util/translate" "github.com/pkg/errors" @@ -21,7 +21,7 @@ import ( "k8s.io/klog/v2" ) -func StartLeaderElection(ctx *options.ControllerContext, scheme *runtime.Scheme, run func() error) error { +func StartLeaderElection(ctx *config.ControllerContext, scheme *runtime.Scheme, run func() error) error { localConfig := ctx.LocalManager.GetConfig() // create the event recorder @@ -65,9 +65,9 @@ func StartLeaderElection(ctx *options.ControllerContext, scheme *runtime.Scheme, // try and become the leader and start controller manager loops leaderelection.RunOrDie(ctx.Context, leaderelection.LeaderElectionConfig{ Lock: rl, - LeaseDuration: time.Duration(ctx.Options.LeaseDuration) * time.Second, - RenewDeadline: time.Duration(ctx.Options.RenewDeadline) * time.Second, - RetryPeriod: time.Duration(ctx.Options.RetryPeriod) * time.Second, + LeaseDuration: time.Duration(ctx.Config.ControlPlane.StatefulSet.HighAvailability.LeaseDuration) * time.Second, + RenewDeadline: time.Duration(ctx.Config.ControlPlane.StatefulSet.HighAvailability.RenewDeadline) * time.Second, + RetryPeriod: time.Duration(ctx.Config.ControlPlane.StatefulSet.HighAvailability.RetryPeriod) * time.Second, Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(_ context.Context) { klog.Info("Acquired leadership and run vcluster in leader mode") diff --git a/pkg/metricsapiservice/register.go b/pkg/metricsapiservice/register.go index 25e3ba9ab..cc50e1a5f 100644 --- a/pkg/metricsapiservice/register.go +++ b/pkg/metricsapiservice/register.go @@ -5,7 +5,7 @@ import ( "math" "time" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -53,7 +53,7 @@ func applyOperation(ctx context.Context, operationFunc wait.ConditionWithContext }, operationFunc) } -func deleteOperation(ctrlCtx *options.ControllerContext) wait.ConditionWithContextFunc { +func deleteOperation(ctrlCtx *config.ControllerContext) wait.ConditionWithContextFunc { return func(ctx context.Context) (bool, error) { err := ctrlCtx.VirtualManager.GetClient().Delete(ctx, &apiregistrationv1.APIService{ ObjectMeta: metav1.ObjectMeta{ @@ -73,7 +73,7 @@ func deleteOperation(ctrlCtx *options.ControllerContext) wait.ConditionWithConte } } -func createOperation(ctrlCtx *options.ControllerContext) wait.ConditionWithContextFunc { +func createOperation(ctrlCtx *config.ControllerContext) wait.ConditionWithContextFunc { return func(ctx context.Context) (bool, error) { service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -134,12 +134,12 @@ func createOperation(ctrlCtx *options.ControllerContext) wait.ConditionWithConte } } -func RegisterOrDeregisterAPIService(ctx *options.ControllerContext) error { +func RegisterOrDeregisterAPIService(ctx *config.ControllerContext) error { // check if the api service should get created exists := checkExistingAPIService(ctx.Context, ctx.VirtualManager.GetClient()) - if ctx.Options.ProxyMetricsServer { + if ctx.Config.Observability.Metrics.Proxy.Nodes || ctx.Config.Observability.Metrics.Proxy.Pods { return applyOperation(ctx.Context, createOperation(ctx)) - } else if !ctx.Options.ProxyMetricsServer && exists { + } else if exists { return applyOperation(ctx.Context, deleteOperation(ctx)) } diff --git a/pkg/options/options.go b/pkg/options/options.go deleted file mode 100644 index 84f8a6b9d..000000000 --- a/pkg/options/options.go +++ /dev/null @@ -1,105 +0,0 @@ -package options - -const ( - DefaultHostsRewriteImage = "library/alpine:3.13.1" - GenericConfig = "CONFIG" -) - -// VirtualClusterOptions holds the cmd flags -type VirtualClusterOptions struct { - // PRO Options - ProOptions VirtualClusterProOptions `json:",inline"` - - // OSS Options below - Controllers []string `json:"controllers,omitempty"` - - ServerCaCert string `json:"serverCaCert,omitempty"` - ServerCaKey string `json:"serverCaKey,omitempty"` - TLSSANs []string `json:"tlsSans,omitempty"` - RequestHeaderCaCert string `json:"requestHeaderCaCert,omitempty"` - ClientCaCert string `json:"clientCaCert,omitempty"` - KubeConfigPath string `json:"kubeConfig,omitempty"` - - KubeConfigContextName string `json:"kubeConfigContextName,omitempty"` - KubeConfigSecret string `json:"kubeConfigSecret,omitempty"` - KubeConfigSecretNamespace string `json:"kubeConfigSecretNamespace,omitempty"` - KubeConfigServer string `json:"kubeConfigServer,omitempty"` - Tolerations []string `json:"tolerations,omitempty"` - - BindAddress string `json:"bindAddress,omitempty"` - Port int `json:"port,omitempty"` - - Name string `json:"name,omitempty"` - - TargetNamespace string `json:"targetNamespace,omitempty"` - ServiceName string `json:"serviceName,omitempty"` - - SetOwner bool `json:"setOwner,omitempty"` - - SyncAllNodes bool `json:"syncAllNodes,omitempty"` - EnableScheduler bool `json:"enableScheduler,omitempty"` - DisableFakeKubelets bool `json:"disableFakeKubelets,omitempty"` - FakeKubeletIPs bool `json:"fakeKubeletIPs,omitempty"` - ClearNodeImages bool `json:"clearNodeImages,omitempty"` - TranslateImages []string `json:"translateImages,omitempty"` - - NodeSelector string `json:"nodeSelector,omitempty"` - EnforceNodeSelector bool `json:"enforceNodeSelector,omitempty"` - ServiceAccount string `json:"serviceAccount,omitempty"` - - OverrideHosts bool `json:"overrideHosts,omitempty"` - OverrideHostsContainerImage string `json:"overrideHostsContainerImage,omitempty"` - - ClusterDomain string `json:"clusterDomain,omitempty"` - - LeaderElect bool `json:"leaderElect,omitempty"` - LeaseDuration int64 `json:"leaseDuration,omitempty"` - RenewDeadline int64 `json:"renewDeadline,omitempty"` - RetryPeriod int64 `json:"retryPeriod,omitempty"` - - DisablePlugins bool `json:"disablePlugins,omitempty"` - PluginListenAddress string `json:"pluginListenAddress,omitempty"` - Plugins []string `json:"plugins,omitempty"` - - DefaultImageRegistry string `json:"defaultImageRegistry,omitempty"` - - EnforcePodSecurityStandard string `json:"enforcePodSecurityStandard,omitempty"` - - MapHostServices []string `json:"mapHostServices,omitempty"` - MapVirtualServices []string `json:"mapVirtualServices,omitempty"` - - SyncLabels []string `json:"syncLabels,omitempty"` - - // hostpath mapper options - // this is only needed if using vcluster-hostpath-mapper component - // see: https://github.com/loft-sh/vcluster-hostpath-mapper - MountPhysicalHostPaths bool `json:"mountPhysicalHostPaths,omitempty"` - // To enable FSMounts functionality - VirtualLogsPath string - VirtualPodLogsPath string - VirtualContainerLogsPath string - VirtualKubeletPodPath string - - HostMetricsBindAddress string `json:"hostMetricsBindAddress,omitempty"` - VirtualMetricsBindAddress string `json:"virtualMetricsBindAddress,omitempty"` - - MultiNamespaceMode bool `json:"multiNamespaceMode,omitempty"` - NamespaceLabels []string `json:"namespaceLabels,omitempty"` - SyncAllSecrets bool `json:"syncAllSecrets,omitempty"` - SyncAllConfigMaps bool `json:"syncAllConfigMaps,omitempty"` - - ProxyMetricsServer bool `json:"proxyMetricsServer,omitempty"` - ServiceAccountTokenSecrets bool `json:"serviceAccountTokenSecrets,omitempty"` - - // DEPRECATED FLAGS - RewriteHostPaths bool `json:"rewriteHostPaths,omitempty"` - DeprecatedSyncNodeChanges bool `json:"syncNodeChanges"` - DeprecatedDisableSyncResources string - DeprecatedOwningStatefulSet string - DeprecatedUseFakeNodes bool - DeprecatedUseFakePersistentVolumes bool - DeprecatedEnableStorageClasses bool - DeprecatedEnablePriorityClasses bool - DeprecatedSuffix string - DeprecatedUseFakeKubelets bool -} diff --git a/pkg/options/pro_options.go b/pkg/options/pro_options.go deleted file mode 100644 index b3e01a850..000000000 --- a/pkg/options/pro_options.go +++ /dev/null @@ -1,19 +0,0 @@ -package options - -type VirtualClusterProOptions struct { - ProLicenseSecret string `json:"proLicenseSecret,omitempty"` - - RemoteKubeConfig string `json:"remoteKubeConfig,omitempty"` - RemoteNamespace string `json:"remoteNamespace,omitempty"` - RemoteServiceName string `json:"remoteServiceName,omitempty"` - EnforceValidatingHooks []string `json:"enforceValidatingHooks"` - EnforceMutatingHooks []string `json:"enforceMutatingHooks"` - EtcdReplicas int `json:"etcdReplicas,omitempty"` - IntegratedCoredns bool `json:"integratedCoreDNS,omitempty"` - UseCoreDNSPlugin bool `json:"useCoreDNSPlugin,omitempty"` - EtcdEmbedded bool `json:"etcdEmbedded,omitempty"` - MigrateFrom string `json:"migrateFrom,omitempty"` - - NoopSyncer bool `json:"noopSyncer,omitempty"` - SyncKubernetesService bool `json:"synck8sService,omitempty"` -} diff --git a/pkg/patches/conditions.go b/pkg/patches/conditions.go index dcc128dbb..1f322af81 100644 --- a/pkg/patches/conditions.go +++ b/pkg/patches/conditions.go @@ -1,7 +1,7 @@ package patches import ( - "github.com/loft-sh/vcluster/pkg/config" + "github.com/loft-sh/vcluster/config" "github.com/pkg/errors" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" yaml "gopkg.in/yaml.v3" diff --git a/pkg/patches/patch.go b/pkg/patches/patch.go index e679fdf75..9d06e7859 100644 --- a/pkg/patches/patch.go +++ b/pkg/patches/patch.go @@ -5,10 +5,10 @@ import ( "fmt" "regexp" + vclusterconfig "github.com/loft-sh/vcluster/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" jsonyaml "github.com/ghodss/yaml" - "github.com/loft-sh/vcluster/pkg/config" "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" "sigs.k8s.io/controller-runtime/pkg/client" @@ -23,7 +23,7 @@ type NameResolver interface { TranslateNamespaceRef(namespace string) (string, error) } -func ApplyPatches(destObj, sourceObj client.Object, patchesConf []*config.Patch, reversePatchesConf []*config.Patch, nameResolver NameResolver) error { +func ApplyPatches(destObj, sourceObj client.Object, patchesConf []*vclusterconfig.Patch, reversePatchesConf []*vclusterconfig.Patch, nameResolver NameResolver) error { node1, err := NewJSONNode(destObj) if err != nil { return errors.Wrap(err, "new json yaml node") @@ -50,8 +50,8 @@ func ApplyPatches(destObj, sourceObj client.Object, patchesConf []*config.Patch, continue } - err := applyPatch(node1, node2, &config.Patch{ - Operation: config.PatchTypeRemove, + err := applyPatch(node1, node2, &vclusterconfig.Patch{ + Operation: vclusterconfig.PatchTypeRemove, Path: p.Path, }, nameResolver) if err != nil { @@ -72,23 +72,23 @@ func ApplyPatches(destObj, sourceObj client.Object, patchesConf []*config.Patch, return nil } -func applyPatch(obj1, obj2 *yaml.Node, patch *config.Patch, resolver NameResolver) error { +func applyPatch(obj1, obj2 *yaml.Node, patch *vclusterconfig.Patch, resolver NameResolver) error { switch patch.Operation { - case config.PatchTypeRewriteName: + case vclusterconfig.PatchTypeRewriteName: return RewriteName(obj1, patch, resolver) - case config.PatchTypeRewriteLabelKey: + case vclusterconfig.PatchTypeRewriteLabelKey: return RewriteLabelKey(obj1, patch, resolver) - case config.PatchTypeRewriteLabelExpressionsSelector: + case vclusterconfig.PatchTypeRewriteLabelExpressionsSelector: return RewriteLabelExpressionsSelector(obj1, patch, resolver) - case config.PatchTypeRewriteLabelSelector: + case vclusterconfig.PatchTypeRewriteLabelSelector: return RewriteLabelSelector(obj1, patch, resolver) - case config.PatchTypeReplace: + case vclusterconfig.PatchTypeReplace: return Replace(obj1, patch) - case config.PatchTypeRemove: + case vclusterconfig.PatchTypeRemove: return Remove(obj1, patch) - case config.PatchTypeAdd: + case vclusterconfig.PatchTypeAdd: return Add(obj1, patch) - case config.PatchTypeCopyFromObject: + case vclusterconfig.PatchTypeCopyFromObject: return CopyFromObject(obj1, obj2, patch) } diff --git a/pkg/patches/patch_test.go b/pkg/patches/patch_test.go index cee53c637..03f88210e 100644 --- a/pkg/patches/patch_test.go +++ b/pkg/patches/patch_test.go @@ -7,10 +7,10 @@ import ( "strings" "testing" + "github.com/loft-sh/vcluster/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "github.com/loft-sh/vcluster/pkg/config" patchesregex "github.com/loft-sh/vcluster/pkg/patches/regex" "github.com/loft-sh/vcluster/pkg/util/translate" yaml "gopkg.in/yaml.v3" diff --git a/pkg/patches/patch_types.go b/pkg/patches/patch_types.go index e3815d489..aaec4d451 100644 --- a/pkg/patches/patch_types.go +++ b/pkg/patches/patch_types.go @@ -4,9 +4,9 @@ import ( "fmt" "strconv" + "github.com/loft-sh/vcluster/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/loft-sh/vcluster/pkg/config" "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" k8syaml "sigs.k8s.io/yaml" diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index b47cd720f..287685188 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" plugintypes "github.com/loft-sh/vcluster/pkg/plugin/types" pluginv1 "github.com/loft-sh/vcluster/pkg/plugin/v1" pluginv2 "github.com/loft-sh/vcluster/pkg/plugin/v2" @@ -34,14 +34,19 @@ func (m *manager) Start( virtualKubeConfig *rest.Config, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + vConfig *config.VirtualClusterConfig, ) error { - err := m.legacyManager.Start(ctx, currentNamespace, targetNamespace, virtualKubeConfig, physicalKubeConfig, syncerConfig, options) + legacyOptions, err := vConfig.LegacyOptions() + if err != nil { + return fmt.Errorf("build legacy options: %w", err) + } + + err = m.legacyManager.Start(ctx, currentNamespace, targetNamespace, virtualKubeConfig, physicalKubeConfig, syncerConfig, legacyOptions) if err != nil { return fmt.Errorf("start legacy plugins: %w", err) } - err = m.pluginManager.Start(ctx, currentNamespace, physicalKubeConfig, syncerConfig, options) + err = m.pluginManager.Start(ctx, currentNamespace, physicalKubeConfig, syncerConfig, vConfig) if err != nil { return fmt.Errorf("start plugins: %w", err) } diff --git a/pkg/plugin/types/types.go b/pkg/plugin/types/types.go index a40f45a2c..8b1143d51 100644 --- a/pkg/plugin/types/types.go +++ b/pkg/plugin/types/types.go @@ -3,7 +3,7 @@ package types import ( "context" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" @@ -18,7 +18,7 @@ type Manager interface { virtualKubeConfig *rest.Config, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + config *config.VirtualClusterConfig, ) error // SetLeader sets the leader for the plugins diff --git a/pkg/plugin/v1/plugin.go b/pkg/plugin/v1/plugin.go index b3467be03..36f3904d4 100644 --- a/pkg/plugin/v1/plugin.go +++ b/pkg/plugin/v1/plugin.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" plugintypes "github.com/loft-sh/vcluster/pkg/plugin/types" "github.com/loft-sh/vcluster/pkg/util/kubeconfig" "github.com/loft-sh/vcluster/pkg/util/loghelper" @@ -166,7 +166,7 @@ func (m *Manager) Start( virtualKubeConfig *rest.Config, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + options *config.LegacyVirtualClusterOptions, ) error { // set if we have plugins m.hasPlugins.Store(len(options.Plugins) > 0) @@ -239,7 +239,7 @@ func (m *Manager) Start( return m.waitForPlugins(ctx, options) } -func (m *Manager) waitForPlugins(ctx context.Context, options *options.VirtualClusterOptions) error { +func (m *Manager) waitForPlugins(ctx context.Context, options *config.LegacyVirtualClusterOptions) error { for _, plugin := range options.Plugins { klog.Infof("Waiting for plugin %s to register...", plugin) err := wait.PollUntilContextTimeout(ctx, time.Millisecond*100, time.Minute*10, true, func(context.Context) (done bool, err error) { diff --git a/pkg/plugin/v2/config.go b/pkg/plugin/v2/config.go index 0522b7bb7..b4415d1f7 100644 --- a/pkg/plugin/v2/config.go +++ b/pkg/plugin/v2/config.go @@ -8,8 +8,11 @@ type InitConfig struct { PhysicalClusterConfig []byte `json:"physicalClusterConfig,omitempty"` SyncerConfig []byte `json:"syncerConfig,omitempty"` CurrentNamespace string `json:"currentNamespace,omitempty"` - Options []byte `json:"options,omitempty"` - WorkingDir string `json:"workingDir,omitempty"` + + Config []byte `json:"config,omitempty"` + Options []byte `json:"options,omitempty"` + + WorkingDir string `json:"workingDir,omitempty"` } // InitConfigPro is used to signal the plugin if vCluster.Pro is enabled and what features are allowed diff --git a/pkg/plugin/v2/plugin.go b/pkg/plugin/v2/plugin.go index 2003624dd..1a67b9f6e 100644 --- a/pkg/plugin/v2/plugin.go +++ b/pkg/plugin/v2/plugin.go @@ -13,7 +13,7 @@ import ( "github.com/ghodss/yaml" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" plugintypes "github.com/loft-sh/vcluster/pkg/plugin/types" "github.com/loft-sh/vcluster/pkg/plugin/v2/pluginv2" "github.com/loft-sh/vcluster/pkg/util/kubeconfig" @@ -70,7 +70,7 @@ func (m *Manager) Start( currentNamespace string, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + vConfig *config.VirtualClusterConfig, ) error { // try to search for plugins plugins, err := m.findPlugins(ctx) @@ -82,7 +82,7 @@ func (m *Manager) Start( // loop over plugins and load them for _, pluginPath := range plugins { - err = m.loadPlugin(pluginPath) + err = m.loadPlugin(pluginPath, vConfig) if err != nil { return fmt.Errorf("start plugin %s: %w", pluginPath, err) } @@ -91,7 +91,7 @@ func (m *Manager) Start( // after loading all plugins we start them for _, vClusterPlugin := range m.Plugins { // build the start request - initRequest, err := m.buildInitRequest(filepath.Dir(vClusterPlugin.Path), currentNamespace, physicalKubeConfig, syncerConfig, options) + initRequest, err := m.buildInitRequest(filepath.Dir(vClusterPlugin.Path), currentNamespace, physicalKubeConfig, syncerConfig, vConfig) if err != nil { return fmt.Errorf("build start request: %w", err) } @@ -240,10 +240,22 @@ func (m *Manager) buildInitRequest( currentNamespace string, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + vConfig *config.VirtualClusterConfig, ) (*pluginv2.Initialize_Request, error) { - // Context options - encodedOptions, err := json.Marshal(options) + // encode config + encodedConfig, err := json.Marshal(vConfig) + if err != nil { + return nil, fmt.Errorf("encode config: %w", err) + } + + // convert to legacy options + legacyOptions, err := vConfig.LegacyOptions() + if err != nil { + return nil, fmt.Errorf("get legacy options: %w", err) + } + + // We need this for downward compatibility + encodedLegacyOptions, err := json.Marshal(legacyOptions) if err != nil { return nil, fmt.Errorf("marshal options: %w", err) } @@ -277,7 +289,8 @@ func (m *Manager) buildInitRequest( PhysicalClusterConfig: phyisicalConfigBytes, SyncerConfig: syncerConfigBytes, CurrentNamespace: currentNamespace, - Options: encodedOptions, + Config: encodedConfig, + Options: encodedLegacyOptions, WorkingDir: workingDir, }) if err != nil { @@ -289,7 +302,7 @@ func (m *Manager) buildInitRequest( }, nil } -func (m *Manager) loadPlugin(pluginPath string) error { +func (m *Manager) loadPlugin(pluginPath string, vConfig *config.VirtualClusterConfig) error { // Create an hclog.Logger logger := hclog.New(&hclog.LoggerOptions{ Name: "plugin", @@ -298,7 +311,7 @@ func (m *Manager) loadPlugin(pluginPath string) error { }) // build command - cmd, err := buildCommand(pluginPath) + cmd, err := buildCommand(pluginPath, vConfig) if err != nil { return err } @@ -375,36 +388,44 @@ func (m *Manager) findPlugins(ctx context.Context) ([]string, error) { return pluginPaths, nil } -func buildCommand(pluginPath string) (*exec.Cmd, error) { - cmd := exec.Command(pluginPath) - - // check for plugin config +func buildCommand(pluginPath string, vConfig *config.VirtualClusterConfig) (*exec.Cmd, error) { pluginName := filepath.Base(filepath.Dir(pluginPath)) - pluginConfig := os.Getenv(PluginConfigEnv) - if pluginConfig == "" { - cmd.Env = os.Environ() - return cmd, nil - } + pluginConfig := "" + + // legacy plugin + if vConfig.Plugin != nil { + legacyPlugin, ok := vConfig.Plugin[pluginName] + if ok && legacyPlugin.Version == "v2" { + pluginConfigEncoded, err := yaml.Marshal(legacyPlugin.Config) + if err != nil { + return nil, fmt.Errorf("encode plugin config: %w", err) + } - // try to parse yaml - parsedConfig := map[string]interface{}{} - err := yaml.Unmarshal([]byte(pluginConfig), &parsedConfig) - if err != nil { - return nil, fmt.Errorf("error parsing %s: %w", PluginConfigEnv, err) + pluginConfig = string(pluginConfigEncoded) + } } - // check if plugin is there - pluginConfigEncoded := "" - if parsedConfig[pluginName] != nil { - pluginConfigRaw, err := yaml.Marshal(parsedConfig[pluginName]) - if err != nil { - return nil, fmt.Errorf("error marshalling plugin config: %w", err) + // new plugin + if vConfig.Plugins != nil { + newPlugin, ok := vConfig.Plugins[pluginName] + if ok { + pluginConfigEncoded, err := yaml.Marshal(newPlugin.Config) + if err != nil { + return nil, fmt.Errorf("encode plugin config: %w", err) + } + + pluginConfig = string(pluginConfigEncoded) } + } - pluginConfigEncoded = string(pluginConfigRaw) + // check for plugin config + cmd := exec.Command(pluginPath) + if pluginConfig == "" { + cmd.Env = os.Environ() + return cmd, nil } // add to plugin environment - cmd.Env = append(os.Environ(), PluginConfigEnv+"="+pluginConfigEncoded) + cmd.Env = append(os.Environ(), PluginConfigEnv+"="+pluginConfig) return cmd, nil } diff --git a/pkg/plugin/v2/types.go b/pkg/plugin/v2/types.go index 527d8bed0..6fe574e4f 100644 --- a/pkg/plugin/v2/types.go +++ b/pkg/plugin/v2/types.go @@ -3,7 +3,7 @@ package v2 import ( "context" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" plugintypes "github.com/loft-sh/vcluster/pkg/plugin/types" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -19,7 +19,7 @@ type Plugin interface { virtualKubeConfig *rest.Config, physicalKubeConfig *rest.Config, syncerConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + vConfig *config.VirtualClusterConfig, ) error // SetLeader signals the plugin that the syncer acquired leadership and starts executing controllers diff --git a/pkg/pro/flags.go b/pkg/pro/flags.go deleted file mode 100644 index 28af7681a..000000000 --- a/pkg/pro/flags.go +++ /dev/null @@ -1,26 +0,0 @@ -package pro - -import ( - "github.com/loft-sh/vcluster/pkg/options" - "github.com/spf13/pflag" -) - -func AddProFlags(flags *pflag.FlagSet, options *options.VirtualClusterOptions) { - flags.StringVar(&options.ProOptions.ProLicenseSecret, "pro-license-secret", "", "If set, vCluster.Pro will try to find this secret to retrieve the vCluster.Pro license.") - - flags.StringVar(&options.ProOptions.RemoteKubeConfig, "remote-kube-config", "", "If set, will use the remote kube-config instead of the local in-cluster one. Expects a kube config to a headless vcluster installation") - flags.StringVar(&options.ProOptions.RemoteNamespace, "remote-namespace", "", "If set, will use this as the remote namespace") - flags.StringVar(&options.ProOptions.RemoteServiceName, "remote-service-name", "", "If set, will use this as the remote service name") - - flags.BoolVar(&options.ProOptions.IntegratedCoredns, "integrated-coredns", false, "If enabled vcluster will spin an in memory coreDNS inside the syncer container") - flags.BoolVar(&options.ProOptions.UseCoreDNSPlugin, "use-coredns-plugin", false, "If enabled, the vcluster plugin for coredns will be used") - flags.BoolVar(&options.ProOptions.NoopSyncer, "noop-syncer", false, "If enabled will setup a noop Syncer that filters and proxies requests to a specified remote cluster") - flags.BoolVar(&options.ProOptions.SyncKubernetesService, "sync-k8s-service", false, "If enabled will sync the kubernetes service endpoints in the remote cluster with the load balancer ip of this cluster") - - flags.BoolVar(&options.ProOptions.EtcdEmbedded, "etcd-embedded", false, "If true, will start an embedded etcd within vCluster") - flags.StringVar(&options.ProOptions.MigrateFrom, "migrate-from", "", "The url (including protocol) of the original database") - flags.IntVar(&options.ProOptions.EtcdReplicas, "etcd-replicas", 0, "The amount of replicas the etcd has") - - flags.StringArrayVar(&options.ProOptions.EnforceValidatingHooks, "enforce-validating-hook", nil, "A validating hook configuration in yaml format encoded with base64. Can be used multiple times") - flags.StringArrayVar(&options.ProOptions.EnforceMutatingHooks, "enforce-mutating-hook", nil, "A mutating hook configuration in yaml format encoded with base64. Can be used multiple times") -} diff --git a/pkg/pro/generic.go b/pkg/pro/generic.go index b5973cb31..af3f31d90 100644 --- a/pkg/pro/generic.go +++ b/pkg/pro/generic.go @@ -1,20 +1,20 @@ package pro import ( - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/blockingcacheclient" "github.com/loft-sh/vcluster/pkg/util/pluginhookclient" "sigs.k8s.io/controller-runtime/pkg/client" ) -var InitProControllerContext = func(_ *options.ControllerContext) error { +var InitProControllerContext = func(_ *config.ControllerContext) error { return nil } -var NewPhysicalClient = func(_ *options.VirtualClusterOptions) client.NewClientFunc { +var NewPhysicalClient = func(_ *config.VirtualClusterConfig) client.NewClientFunc { return pluginhookclient.NewPhysicalPluginClientFactory(blockingcacheclient.NewCacheClient) } -var NewVirtualClient = func(_ *options.VirtualClusterOptions) client.NewClientFunc { +var NewVirtualClient = func(_ *config.VirtualClusterConfig) client.NewClientFunc { return pluginhookclient.NewVirtualPluginClientFactory(blockingcacheclient.NewCacheClient) } diff --git a/pkg/pro/integrated_coredns.go b/pkg/pro/integrated_coredns.go index 1e5590cb4..568b10053 100644 --- a/pkg/pro/integrated_coredns.go +++ b/pkg/pro/integrated_coredns.go @@ -1,14 +1,14 @@ package pro import ( - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/specialservices" ) -var StartIntegratedCoreDNS = func(_ *options.ControllerContext) error { +var StartIntegratedCoreDNS = func(_ *config.ControllerContext) error { return NewFeatureError("integrated core dns") } -var InitDNSServiceSyncing = func(_ *options.VirtualClusterOptions) specialservices.Interface { +var InitDNSServiceSyncing = func(_ *config.VirtualClusterConfig) specialservices.Interface { return specialservices.NewDefaultServiceSyncer() } diff --git a/pkg/pro/license.go b/pkg/pro/license.go index 86f7a3de3..5d845efac 100644 --- a/pkg/pro/license.go +++ b/pkg/pro/license.go @@ -3,11 +3,12 @@ package pro import ( "context" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/client-go/rest" ) // LicenseInit is used to initialize the license reader -var LicenseInit = func(_ context.Context, _ *rest.Config, _, _ string) error { +var LicenseInit = func(_ context.Context, _ *rest.Config, _ string, _ *config.VirtualClusterConfig) error { return nil } diff --git a/pkg/pro/noop.go b/pkg/pro/noop.go index 6b4f93190..0c77675a4 100644 --- a/pkg/pro/noop.go +++ b/pkg/pro/noop.go @@ -1,11 +1,11 @@ package pro import ( - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) -var SyncNoopSyncerEndpoints = func(_ *options.ControllerContext, _ types.NamespacedName, _ client.Client, _ types.NamespacedName, _ string) error { +var SyncNoopSyncerEndpoints = func(_ *config.ControllerContext, _ types.NamespacedName, _ client.Client, _ types.NamespacedName, _ string) error { return NewFeatureError("noop syncer") } diff --git a/pkg/pro/remote.go b/pkg/pro/remote.go index 438a993b6..4bf5367d9 100644 --- a/pkg/pro/remote.go +++ b/pkg/pro/remote.go @@ -4,7 +4,7 @@ import ( "context" "github.com/loft-sh/admin-apis/pkg/licenseapi" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/util/clienthelper" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -var GetRemoteClient = func(options *options.VirtualClusterOptions) (*rest.Config, string, string, *rest.Config, string, string, error) { +var GetRemoteClient = func(vConfig *config.VirtualClusterConfig) (*rest.Config, string, string, *rest.Config, string, string, error) { inClusterConfig := ctrl.GetConfigOrDie() inClusterConfig.QPS = 40 inClusterConfig.Burst = 80 @@ -26,19 +26,18 @@ var GetRemoteClient = func(options *options.VirtualClusterOptions) (*rest.Config } // check if remote cluster - proOptions := options.ProOptions - if proOptions.RemoteKubeConfig == "" { - return inClusterConfig, currentNamespace, options.ServiceName, inClusterConfig, currentNamespace, options.ServiceName, nil + if vConfig.Experimental.IsolatedControlPlane.Enabled { + return nil, "", "", nil, "", "", NewFeatureError(string(licenseapi.VirtualClusterProDistroIsolatedControlPlane)) } - return nil, "", "", nil, "", "", NewFeatureError(string(licenseapi.VirtualClusterProDistroIsolatedControlPlane)) + return inClusterConfig, currentNamespace, vConfig.ServiceName, inClusterConfig, currentNamespace, vConfig.ServiceName, nil } var AddRemoteNodePortSANs = func(_ context.Context, _, _ string, _ kubernetes.Interface) error { return nil } -var ExchangeControlPlaneClient = func(controllerCtx *options.ControllerContext, _ string, _ *rest.Config) (client.Client, error) { +var ExchangeControlPlaneClient = func(controllerCtx *config.ControllerContext, _ string, _ *rest.Config) (client.Client, error) { return controllerCtx.CurrentNamespaceClient, nil } diff --git a/pkg/pro/validation.go b/pkg/pro/validation.go deleted file mode 100644 index 43324fb41..000000000 --- a/pkg/pro/validation.go +++ /dev/null @@ -1,97 +0,0 @@ -package pro - -import ( - "crypto/x509" - "encoding/base64" - "errors" - "fmt" - "net/url" - - "github.com/ghodss/yaml" - "github.com/loft-sh/vcluster/pkg/options" - - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" -) - -func ValidateProOptions(options *options.VirtualClusterOptions) error { - _, _, err := ParseExtraHooks(options.ProOptions.EnforceValidatingHooks, options.ProOptions.EnforceMutatingHooks) - return err -} - -func ParseExtraHooks(valHooks, mutHooks []string) ([]admissionregistrationv1.ValidatingWebhookConfiguration, []admissionregistrationv1.MutatingWebhookConfiguration, error) { - decodedVal := make([]string, 0, len(valHooks)) - for _, v := range valHooks { - bytes, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, nil, err - } - decodedVal = append(decodedVal, string(bytes)) - } - decodedMut := make([]string, 0, len(mutHooks)) - for _, v := range mutHooks { - bytes, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return nil, nil, err - } - decodedMut = append(decodedMut, string(bytes)) - } - - validateConfs := make([]admissionregistrationv1.ValidatingWebhookConfiguration, 0, len(valHooks)) - mutateConfs := make([]admissionregistrationv1.MutatingWebhookConfiguration, 0, len(mutHooks)) - for _, v := range decodedVal { - var valHook admissionregistrationv1.ValidatingWebhookConfiguration - err := yaml.Unmarshal([]byte(v), &valHook) - if err != nil { - return nil, nil, err - } - for _, v := range valHook.Webhooks { - err := validateWebhookClientCfg(v.ClientConfig) - if err != nil { - return nil, nil, fmt.Errorf("webhook client config was not valid for ValidatingWebhookConfiguration %s: %w", v.Name, err) - } - } - validateConfs = append(validateConfs, valHook) - } - for _, v := range decodedMut { - var mutHook admissionregistrationv1.MutatingWebhookConfiguration - err := yaml.Unmarshal([]byte(v), &mutHook) - if err != nil { - return nil, nil, err - } - for _, v := range mutHook.Webhooks { - err := validateWebhookClientCfg(v.ClientConfig) - if err != nil { - return nil, nil, fmt.Errorf("webhook client config was not valid for MutatingWebhookConfiguration %s: %w", v.Name, err) - } - } - mutateConfs = append(mutateConfs, mutHook) - } - - return validateConfs, mutateConfs, nil -} - -func validateWebhookClientCfg(clientCfg admissionregistrationv1.WebhookClientConfig) error { - if len(clientCfg.CABundle) != 0 { - ok := x509.NewCertPool().AppendCertsFromPEM(clientCfg.CABundle) - if !ok { - return errors.New("could not parse the CABundle") - } - } - - if clientCfg.Service == nil && clientCfg.URL == nil { - return errors.New("there is no service config") - } - - if clientCfg.Service != nil && (clientCfg.Service.Name == "" || clientCfg.Service.Namespace == "") { - return errors.New("namespace or name of the service is missing") - } - - if clientCfg.URL != nil { - _, err := url.Parse(*clientCfg.URL) - if err != nil { - return errors.New("the url was not valid") - } - } - - return nil -} diff --git a/pkg/server/cert/syncer.go b/pkg/server/cert/syncer.go index 8c6d97f9a..5c74967f9 100644 --- a/pkg/server/cert/syncer.go +++ b/pkg/server/cert/syncer.go @@ -9,10 +9,9 @@ import ( "sync" "time" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/constants" "github.com/loft-sh/vcluster/pkg/controllers/resources/nodes/nodeservice" - "github.com/loft-sh/vcluster/pkg/options" - "github.com/loft-sh/vcluster/pkg/util/translate" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,16 +33,16 @@ type Syncer interface { dynamiccertificates.CertKeyContentProvider } -func NewSyncer(_ context.Context, currentNamespace string, currentNamespaceClient client.Client, options *options.VirtualClusterOptions) (Syncer, error) { +func NewSyncer(_ context.Context, currentNamespace string, currentNamespaceClient client.Client, options *config.VirtualClusterConfig) (Syncer, error) { return &syncer{ - clusterDomain: options.ClusterDomain, + clusterDomain: options.Networking.Advanced.ClusterDomain, - serverCaKey: options.ServerCaKey, - serverCaCert: options.ServerCaCert, + serverCaKey: options.VirtualClusterKubeConfig().ServerCAKey, + serverCaCert: options.VirtualClusterKubeConfig().ServerCACert, - fakeKubeletIPs: options.FakeKubeletIPs, + fakeKubeletIPs: options.Networking.Advanced.ProxyKubelets.ByIP, - addSANs: options.TLSSANs, + addSANs: options.ControlPlane.Proxy.ExtraSANs, listeners: []dynamiccertificates.Listener{}, serviceName: options.ServiceName, @@ -111,7 +110,7 @@ func (s *syncer) getSANs(ctx context.Context) ([]string, error) { } // get load balancer ip - // currently, the load balancer service is named -lb, but the syncer image might run in legacy environments + // currently, the load balancer service is named , but the syncer image might run in legacy environments // where the load balancer service is the same service, the service is only updated if the helm template is rerun, // so we are leaving this snippet in, but the load balancer ip will be read via the lbSVC var below for _, ing := range svc.Status.LoadBalancer.Ingress { @@ -143,18 +142,17 @@ func (s *syncer) getSANs(ctx context.Context) ([]string, error) { } // get cluster ip of load balancer service - lbSVCName := translate.GetLoadBalancerSVCName(s.serviceName) lbSVC := &corev1.Service{} err = s.currentNamespaceCient.Get(ctx, types.NamespacedName{ Namespace: s.currentNamespace, - Name: lbSVCName, + Name: s.serviceName, }, lbSVC) // proceed only if load balancer service exists if !kerrors.IsNotFound(err) { if err != nil { - return nil, fmt.Errorf("error getting vcluster load balancer service %s/%s: %w", s.currentNamespace, lbSVCName, err) + return nil, fmt.Errorf("error getting vcluster load balancer service %s/%s: %w", s.currentNamespace, s.serviceName, err) } else if lbSVC.Spec.ClusterIP == "" { - return nil, fmt.Errorf("target service %s/%s is missing a clusterIP", s.currentNamespace, lbSVCName) + return nil, fmt.Errorf("target service %s/%s is missing a clusterIP", s.currentNamespace, s.serviceName) } for _, ing := range lbSVC.Status.LoadBalancer.Ingress { @@ -166,9 +164,10 @@ func (s *syncer) getSANs(ctx context.Context) ([]string, error) { } } // append hostnames for load balancer service - retSANs = append(retSANs, - lbSVCName, - lbSVCName+"."+s.currentNamespace, "*."+translate.VClusterName+"."+s.currentNamespace+"."+constants.NodeSuffix, + retSANs = append( + retSANs, + s.serviceName, + s.serviceName+"."+s.currentNamespace, "*."+translate.VClusterName+"."+s.currentNamespace+"."+constants.NodeSuffix, ) } diff --git a/pkg/server/server.go b/pkg/server/server.go index a12031701..34d3981d3 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -14,10 +14,10 @@ import ( "github.com/loft-sh/vcluster/pkg/authorization/delegatingauthorizer" "github.com/loft-sh/vcluster/pkg/authorization/impersonationauthorizer" "github.com/loft-sh/vcluster/pkg/authorization/kubeletauthorizer" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/constants" "github.com/loft-sh/vcluster/pkg/controllers/resources/nodes" "github.com/loft-sh/vcluster/pkg/controllers/resources/nodes/nodeservice" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/server/cert" "github.com/loft-sh/vcluster/pkg/server/filters" "github.com/loft-sh/vcluster/pkg/server/handler" @@ -74,7 +74,7 @@ type Server struct { // NewServer creates and installs a new Server. // 'filter', if non-nil, protects requests to the api only. -func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile string) (*Server, error) { +func NewServer(ctx *config.ControllerContext, requestHeaderCaFile, clientCaFile string) (*Server, error) { localConfig := ctx.LocalManager.GetConfig() virtualConfig := ctx.VirtualManager.GetConfig() uncachedLocalClient, err := client.New(localConfig, client.Options{ @@ -93,7 +93,7 @@ func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile } cachedLocalClient, err := createCachedClient(ctx.Context, localConfig, ctx.CurrentNamespace, uncachedLocalClient.RESTMapper(), uncachedLocalClient.Scheme(), func(cache cache.Cache) error { - if ctx.Options.FakeKubeletIPs { + if ctx.Config.Networking.Advanced.ProxyKubelets.ByIP { err := cache.IndexField(ctx.Context, &corev1.Service{}, constants.IndexByClusterIP, func(object client.Object) []string { svc := object.(*corev1.Service) if len(svc.Labels) == 0 || svc.Labels[nodeservice.ServiceClusterLabel] != translate.VClusterName { @@ -145,7 +145,7 @@ func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile uncachedLocalClient = pluginhookclient.WrapPhysicalClient(uncachedLocalClient) cachedLocalClient = pluginhookclient.WrapPhysicalClient(cachedLocalClient) - certSyncer, err := cert.NewSyncer(ctx.Context, ctx.CurrentNamespace, cachedLocalClient, ctx.Options) + certSyncer, err := cert.NewSyncer(ctx.Context, ctx.CurrentNamespace, cachedLocalClient, ctx.Config) if err != nil { return nil, errors.Wrap(err, "create cert syncer") } @@ -156,7 +156,7 @@ func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile certSyncer: certSyncer, handler: http.NewServeMux(), - fakeKubeletIPs: ctx.Options.FakeKubeletIPs, + fakeKubeletIPs: ctx.Config.Networking.Advanced.ProxyKubelets.ByIP, currentNamespace: ctx.CurrentNamespace, currentNamespaceClient: cachedLocalClient, @@ -199,24 +199,24 @@ func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile } h := handler.ImpersonatingHandler("", virtualConfig) - h = filters.WithServiceCreateRedirect(h, uncachedLocalClient, uncachedVirtualClient, virtualConfig, ctx.Options.SyncLabels) + h = filters.WithServiceCreateRedirect(h, uncachedLocalClient, uncachedVirtualClient, virtualConfig, ctx.Config.Experimental.SyncSettings.SyncLabels) h = filters.WithRedirect(h, localConfig, uncachedLocalClient.Scheme(), uncachedVirtualClient, admissionHandler, s.redirectResources) h = filters.WithMetricsProxy(h, localConfig, cachedVirtualClient) // is metrics proxy enabled? - if ctx.Options.ProxyMetricsServer { + if ctx.Config.Observability.Metrics.Proxy.Nodes || ctx.Config.Observability.Metrics.Proxy.Pods { h = filters.WithMetricsServerProxy( h, - ctx.Options.TargetNamespace, + ctx.Config.TargetNamespace, cachedLocalClient, cachedVirtualClient, localConfig, virtualConfig, - ctx.Options.MultiNamespaceMode, + ctx.Config.Experimental.MultiNamespaceMode.Enabled, ) } - if ctx.Options.DeprecatedSyncNodeChanges { + if ctx.Config.Sync.FromHost.Nodes.Enabled && ctx.Config.Sync.FromHost.Nodes.SyncBackChanges { h = filters.WithNodeChanges(ctx.Context, h, uncachedLocalClient, uncachedVirtualClient, virtualConfig) } h = filters.WithFakeKubelet(h, localConfig, cachedVirtualClient) @@ -227,9 +227,7 @@ func NewServer(ctx *options.ControllerContext, requestHeaderCaFile, clientCaFile } for _, f := range ctx.AdditionalServerFilters { - h = f(h, servertypes.FilterOptions{ - LocalScheme: uncachedLocalClient.Scheme(), - }) + h = f(h) } for _, handler := range ctx.ExtraHandlers { diff --git a/pkg/server/types/types.go b/pkg/server/types/types.go index 9ef8b7651..9dd4e2220 100644 --- a/pkg/server/types/types.go +++ b/pkg/server/types/types.go @@ -2,15 +2,9 @@ package types import ( "net/http" - - "k8s.io/apimachinery/pkg/runtime" ) -type FilterOptions struct { - LocalScheme *runtime.Scheme -} - -type Filter func(http.Handler, FilterOptions) http.Handler +type Filter func(http.Handler) http.Handler type OriginalUserKeyType int diff --git a/pkg/setup/controller_context.go b/pkg/setup/controller_context.go index 34368859a..c153f9317 100644 --- a/pkg/setup/controller_context.go +++ b/pkg/setup/controller_context.go @@ -6,13 +6,12 @@ import ( "os" "time" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/controllers/resources/nodes" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/plugin" "github.com/loft-sh/vcluster/pkg/pro" "github.com/loft-sh/vcluster/pkg/telemetry" "github.com/loft-sh/vcluster/pkg/util/blockingcacheclient" - "github.com/loft-sh/vcluster/pkg/util/toleration" "github.com/loft-sh/vcluster/pkg/util/translate" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,26 +29,14 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) -var allowedPodSecurityStandards = map[string]bool{ - "privileged": true, - "baseline": true, - "restricted": true, -} - // NewControllerContext builds the controller context we can use to start the syncer func NewControllerContext( ctx context.Context, - options *options.VirtualClusterOptions, + options *config.VirtualClusterConfig, currentNamespace string, inClusterConfig *rest.Config, scheme *runtime.Scheme, -) (*options.ControllerContext, error) { - // validate options - err := ValidateOptions(options) - if err != nil { - return nil, err - } - +) (*config.ControllerContext, error) { // create controller context controllerContext, err := InitManagers( ctx, @@ -75,13 +62,13 @@ func NewControllerContext( func InitManagers( ctx context.Context, - options *options.VirtualClusterOptions, + options *config.VirtualClusterConfig, currentNamespace string, inClusterConfig *rest.Config, scheme *runtime.Scheme, newPhysicalClient client.NewClientFunc, newVirtualClient client.NewClientFunc, -) (*options.ControllerContext, error) { +) (*config.ControllerContext, error) { // load virtual config virtualConfig, virtualRawConfig, err := LoadVirtualConfig(ctx, options) if err != nil { @@ -90,7 +77,7 @@ func InitManagers( // is multi namespace mode? var defaultNamespaces map[string]cache.Config - if options.MultiNamespaceMode { + if options.Experimental.MultiNamespaceMode.Enabled { // set options.TargetNamespace to empty because it will later be used in Manager options.TargetNamespace = "" translate.Default = translate.NewMultiNamespaceTranslator(currentNamespace) @@ -113,7 +100,7 @@ func InitManagers( klog.Info("Using physical cluster at " + inClusterConfig.Host) localManager, err := ctrl.NewManager(inClusterConfig, ctrl.Options{ Scheme: scheme, - Metrics: metricsserver.Options{BindAddress: options.HostMetricsBindAddress}, + Metrics: metricsserver.Options{BindAddress: "0"}, LeaderElection: false, Cache: cache.Options{DefaultNamespaces: defaultNamespaces}, NewClient: newPhysicalClient, @@ -125,7 +112,7 @@ func InitManagers( // create virtual manager virtualClusterManager, err := ctrl.NewManager(virtualConfig, ctrl.Options{ Scheme: scheme, - Metrics: metricsserver.Options{BindAddress: options.VirtualMetricsBindAddress}, + Metrics: metricsserver.Options{BindAddress: "0"}, LeaderElection: false, NewClient: newVirtualClient, }) @@ -143,26 +130,23 @@ func StartPlugins( inClusterConfig, virtualConfig *rest.Config, virtualRawConfig *clientcmdapi.Config, - options *options.VirtualClusterOptions, + options *config.VirtualClusterConfig, ) error { - // start plugins only if they are not disabled - if !options.DisablePlugins { - klog.Infof("Start Plugins Manager...") - syncerConfig, err := CreateVClusterKubeConfig(virtualRawConfig, options) - if err != nil { - return err - } + klog.Infof("Start Plugins Manager...") + syncerConfig, err := CreateVClusterKubeConfig(virtualRawConfig, options) + if err != nil { + return err + } - err = plugin.DefaultManager.Start(ctx, currentNamespace, options.TargetNamespace, virtualConfig, inClusterConfig, syncerConfig, options) - if err != nil { - return err - } + err = plugin.DefaultManager.Start(ctx, currentNamespace, options.TargetNamespace, virtualConfig, inClusterConfig, syncerConfig, options) + if err != nil { + return err } return nil } -func LoadVirtualConfig(ctx context.Context, options *options.VirtualClusterOptions) (*rest.Config, *clientcmdapi.Config, error) { +func LoadVirtualConfig(ctx context.Context, options *config.VirtualClusterConfig) (*rest.Config, *clientcmdapi.Config, error) { // wait for client config clientConfig, err := WaitForClientConfig(ctx, options) if err != nil { @@ -188,38 +172,11 @@ func LoadVirtualConfig(ctx context.Context, options *options.VirtualClusterOptio return virtualClusterConfig, &rawConfig, nil } -func ValidateOptions(options *options.VirtualClusterOptions) error { - // check the value of pod security standard - if options.EnforcePodSecurityStandard != "" && !allowedPodSecurityStandards[options.EnforcePodSecurityStandard] { - return fmt.Errorf("invalid argument enforce-pod-security-standard=%s, must be one of: privileged, baseline, restricted", options.EnforcePodSecurityStandard) - } - - // parse tolerations - for _, t := range options.Tolerations { - _, err := toleration.ParseToleration(t) - if err != nil { - return err - } - } - - // check if enable scheduler works correctly - if options.EnableScheduler && !options.SyncAllNodes && len(options.NodeSelector) == 0 { - options.SyncAllNodes = true - } - - // migrate fake kubelet flag - if !options.DeprecatedUseFakeKubelets { - options.DisableFakeKubelets = true - } - - return nil -} - -func WaitForClientConfig(ctx context.Context, options *options.VirtualClusterOptions) (clientcmd.ClientConfig, error) { +func WaitForClientConfig(ctx context.Context, options *config.VirtualClusterConfig) (clientcmd.ClientConfig, error) { // wait until kube config is available var clientConfig clientcmd.ClientConfig err := wait.PollUntilContextTimeout(ctx, time.Second, time.Hour, true, func(ctx context.Context) (bool, error) { - out, err := os.ReadFile(options.KubeConfigPath) + out, err := os.ReadFile(options.VirtualClusterKubeConfig().KubeConfig) if err != nil { if os.IsNotExist(err) { klog.Info("couldn't find virtual cluster kube-config, will retry in 1 seconds") @@ -266,7 +223,7 @@ func WaitForClientConfig(ctx context.Context, options *options.VirtualClusterOpt return clientConfig, nil } -func CreateVClusterKubeConfig(config *clientcmdapi.Config, options *options.VirtualClusterOptions) (*clientcmdapi.Config, error) { +func CreateVClusterKubeConfig(config *clientcmdapi.Config, options *config.VirtualClusterConfig) (*clientcmdapi.Config, error) { config = config.DeepCopy() // exchange kube config server & resolve certificate @@ -282,10 +239,10 @@ func CreateVClusterKubeConfig(config *clientcmdapi.Config, options *options.Virt config.Clusters[i].CertificateAuthorityData = o } - if options.KubeConfigServer != "" { - config.Clusters[i].Server = options.KubeConfigServer + if options.Config.ExportKubeConfig.Server != "" { + config.Clusters[i].Server = options.Config.ExportKubeConfig.Server } else { - config.Clusters[i].Server = fmt.Sprintf("https://localhost:%d", options.Port) + config.Clusters[i].Server = fmt.Sprintf("https://localhost:%d", options.Config.ControlPlane.Proxy.Port) } } @@ -321,8 +278,8 @@ func InitControllerContext( localManager, virtualManager ctrl.Manager, virtualRawConfig *clientcmdapi.Config, - vClusterOptions *options.VirtualClusterOptions, -) (*options.ControllerContext, error) { + vClusterOptions *config.VirtualClusterConfig, +) (*config.ControllerContext, error) { stopChan := make(<-chan struct{}) // get virtual cluster version @@ -343,25 +300,18 @@ func InitControllerContext( return nil, err } - // parse enabled controllers - controllers, err := ParseControllers(vClusterOptions) - if err != nil { - return nil, err - } - localDiscoveryClient, err := discovery.NewDiscoveryClientForConfig(localManager.GetConfig()) if err != nil { return nil, err } - controllers, err = DisableMissingAPIs(localDiscoveryClient, controllers) + err = vClusterOptions.DisableMissingAPIs(localDiscoveryClient) if err != nil { return nil, err } - return &options.ControllerContext{ + return &config.ControllerContext{ Context: ctx, - Controllers: controllers, LocalManager: localManager, VirtualManager: virtualManager, VirtualRawConfig: virtualRawConfig, @@ -371,11 +321,11 @@ func InitControllerContext( CurrentNamespaceClient: currentNamespaceClient, StopChan: stopChan, - Options: vClusterOptions, + Config: vClusterOptions, }, nil } -func NewCurrentNamespaceClient(ctx context.Context, currentNamespace string, localManager ctrl.Manager, options *options.VirtualClusterOptions) (client.Client, error) { +func NewCurrentNamespaceClient(ctx context.Context, currentNamespace string, localManager ctrl.Manager, options *config.VirtualClusterConfig) (client.Client, error) { var err error // currentNamespaceCache is needed for tasks such as finding out fake kubelet ips diff --git a/pkg/setup/controllers.go b/pkg/setup/controllers.go index 44515e7a8..c24d38b5d 100644 --- a/pkg/setup/controllers.go +++ b/pkg/setup/controllers.go @@ -6,12 +6,12 @@ import ( "math" "time" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/controllers" "github.com/loft-sh/vcluster/pkg/controllers/resources/services" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" "github.com/loft-sh/vcluster/pkg/coredns" "github.com/loft-sh/vcluster/pkg/metricsapiservice" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/plugin" "github.com/loft-sh/vcluster/pkg/pro" "github.com/loft-sh/vcluster/pkg/specialservices" @@ -33,13 +33,11 @@ import ( ) func StartControllers( - controllerContext *options.ControllerContext, + controllerContext *config.ControllerContext, controlPlaneNamespace, controlPlaneService string, controlPlaneConfig *rest.Config, ) error { - proOptions := controllerContext.Options.ProOptions - // exchange control plane client controlPlaneClient, err := pro.ExchangeControlPlaneClient(controllerContext, controlPlaneNamespace, controlPlaneConfig) if err != nil { @@ -48,7 +46,7 @@ func StartControllers( // start coredns & create syncers var syncers []syncertypes.Object - if !proOptions.NoopSyncer { + if !controllerContext.Config.Experimental.SyncSettings.DisableSync { // setup CoreDNS according to the manifest file // skip this if both integrated and dedicated coredns // deployments are explicitly disabled @@ -57,7 +55,7 @@ func StartControllers( ApplyCoreDNS(controllerContext) // delete coredns deployment if integrated core dns - if proOptions.IntegratedCoredns { + if controllerContext.Config.ControlPlane.CoreDNS.Embedded { err := controllerContext.VirtualManager.GetClient().Delete(controllerContext.Context, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "coredns", @@ -84,7 +82,7 @@ func StartControllers( } // sync remote Endpoints - if proOptions.RemoteKubeConfig != "" { + if controllerContext.Config.Experimental.IsolatedControlPlane.KubeConfig != "" { err := pro.SyncRemoteEndpoints( controllerContext.Context, types.NamespacedName{ @@ -94,7 +92,7 @@ func StartControllers( controlPlaneClient, types.NamespacedName{ Namespace: controllerContext.CurrentNamespace, - Name: controllerContext.Options.ServiceName, + Name: controllerContext.Config.ServiceName, }, controllerContext.CurrentNamespaceClient, ) @@ -104,12 +102,12 @@ func StartControllers( } // sync endpoints for noop syncer - if proOptions.NoopSyncer && proOptions.SyncKubernetesService { + if controllerContext.Config.Experimental.SyncSettings.DisableSync && controllerContext.Config.Experimental.SyncSettings.RewriteKubernetesService { err := pro.SyncNoopSyncerEndpoints( controllerContext, types.NamespacedName{ Namespace: controlPlaneNamespace, - Name: controlPlaneService + "-lb", + Name: controlPlaneService, }, controlPlaneClient, types.NamespacedName{ @@ -124,7 +122,7 @@ func StartControllers( } // if not noop syncer - if !proOptions.NoopSyncer { + if !controllerContext.Config.Experimental.SyncSettings.DisableSync { // make sure the kubernetes service is synced err = SyncKubernetesService(controllerContext) if err != nil { @@ -141,7 +139,7 @@ func StartControllers( // write the kube config to secret go func() { wait.Until(func() { - err := WriteKubeConfigToSecret(controllerContext.Context, controlPlaneNamespace, controlPlaneClient, controllerContext.Options, controllerContext.VirtualRawConfig, proOptions.RemoteKubeConfig != "") + err := WriteKubeConfigToSecret(controllerContext.Context, controlPlaneNamespace, controlPlaneClient, controllerContext.Config, controllerContext.VirtualRawConfig) if err != nil { klog.Errorf("Error writing kube config to secret: %v", err) } @@ -149,19 +147,17 @@ func StartControllers( }() // set leader - if !controllerContext.Options.DisablePlugins { - err = plugin.DefaultManager.SetLeader(controllerContext.Context) - if err != nil { - return fmt.Errorf("plugin set leader: %w", err) - } + err = plugin.DefaultManager.SetLeader(controllerContext.Context) + if err != nil { + return fmt.Errorf("plugin set leader: %w", err) } return nil } -func ApplyCoreDNS(controllerContext *options.ControllerContext) { +func ApplyCoreDNS(controllerContext *config.ControllerContext) { _ = wait.ExponentialBackoffWithContext(controllerContext.Context, wait.Backoff{Duration: time.Second, Factor: 1.5, Cap: time.Minute, Steps: math.MaxInt32}, func(ctx context.Context) (bool, error) { - err := coredns.ApplyManifest(ctx, controllerContext.Options.DefaultImageRegistry, controllerContext.VirtualManager.GetConfig(), controllerContext.VirtualClusterVersion) + err := coredns.ApplyManifest(ctx, controllerContext.Config.ControlPlane.Advanced.DefaultImageRegistry, controllerContext.VirtualManager.GetConfig(), controllerContext.VirtualClusterVersion) if err != nil { if errors.Is(err, coredns.ErrNoCoreDNSManifests) { klog.Infof("No CoreDNS manifests found, skipping CoreDNS configuration") @@ -198,7 +194,7 @@ func SetGlobalOwner(ctx context.Context, currentNamespaceClient client.Client, c return nil } -func SyncKubernetesService(ctx *options.ControllerContext) error { +func SyncKubernetesService(ctx *config.ControllerContext) error { err := specialservices.SyncKubernetesService( &synccontext.SyncContext{ Context: ctx.Context, @@ -209,7 +205,7 @@ func SyncKubernetesService(ctx *options.ControllerContext) error { CurrentNamespaceClient: ctx.CurrentNamespaceClient, }, ctx.CurrentNamespace, - ctx.Options.ServiceName, + ctx.Config.ServiceName, types.NamespacedName{ Name: specialservices.DefaultKubernetesSVCName, Namespace: specialservices.DefaultKubernetesSVCNamespace, @@ -227,7 +223,7 @@ func SyncKubernetesService(ctx *options.ControllerContext) error { return nil } -func StartManagers(controllerContext *options.ControllerContext, syncers []syncertypes.Object) error { +func StartManagers(controllerContext *config.ControllerContext, syncers []syncertypes.Object) error { // execute controller initializers to setup prereqs, etc. err := controllers.ExecuteInitializers(controllerContext, syncers) if err != nil { @@ -270,9 +266,9 @@ func StartManagers(controllerContext *options.ControllerContext, syncers []synce controllerContext.Context, controllerContext.CurrentNamespaceClient, controllerContext.CurrentNamespace, - controllerContext.Options.TargetNamespace, - controllerContext.Options.SetOwner, - controllerContext.Options.ServiceName, + controllerContext.Config.TargetNamespace, + controllerContext.Config.Experimental.SyncSettings.SetOwner, + controllerContext.Config.ServiceName, ) if err != nil { return errors.Wrap(err, "finding vcluster pod owner") @@ -281,67 +277,67 @@ func StartManagers(controllerContext *options.ControllerContext, syncers []synce return nil } -func RegisterOrDeregisterAPIService(ctx *options.ControllerContext) { +func RegisterOrDeregisterAPIService(ctx *config.ControllerContext) { err := metricsapiservice.RegisterOrDeregisterAPIService(ctx) if err != nil { klog.Errorf("Error registering metrics apiservice: %v", err) } } -func WriteKubeConfigToSecret(ctx context.Context, currentNamespace string, currentNamespaceClient client.Client, options *options.VirtualClusterOptions, config *clientcmdapi.Config, isRemote bool) error { - config, err := CreateVClusterKubeConfig(config, options) +func WriteKubeConfigToSecret(ctx context.Context, currentNamespace string, currentNamespaceClient client.Client, options *config.VirtualClusterConfig, syncerConfig *clientcmdapi.Config) error { + syncerConfig, err := CreateVClusterKubeConfig(syncerConfig, options) if err != nil { return err } - if options.KubeConfigContextName != "" { - config.CurrentContext = options.KubeConfigContextName + if options.Config.ExportKubeConfig.Context != "" { + syncerConfig.CurrentContext = options.Config.ExportKubeConfig.Context // update authInfo - for k := range config.AuthInfos { - config.AuthInfos[options.KubeConfigContextName] = config.AuthInfos[k] - if k != options.KubeConfigContextName { - delete(config.AuthInfos, k) + for k := range syncerConfig.AuthInfos { + syncerConfig.AuthInfos[syncerConfig.CurrentContext] = syncerConfig.AuthInfos[k] + if k != syncerConfig.CurrentContext { + delete(syncerConfig.AuthInfos, k) } break } // update cluster - for k := range config.Clusters { - config.Clusters[options.KubeConfigContextName] = config.Clusters[k] - if k != options.KubeConfigContextName { - delete(config.Clusters, k) + for k := range syncerConfig.Clusters { + syncerConfig.Clusters[syncerConfig.CurrentContext] = syncerConfig.Clusters[k] + if k != syncerConfig.CurrentContext { + delete(syncerConfig.Clusters, k) } break } // update context - for k := range config.Contexts { - tmpCtx := config.Contexts[k] - tmpCtx.Cluster = options.KubeConfigContextName - tmpCtx.AuthInfo = options.KubeConfigContextName - config.Contexts[options.KubeConfigContextName] = tmpCtx - if k != options.KubeConfigContextName { - delete(config.Contexts, k) + for k := range syncerConfig.Contexts { + tmpCtx := syncerConfig.Contexts[k] + tmpCtx.Cluster = syncerConfig.CurrentContext + tmpCtx.AuthInfo = syncerConfig.CurrentContext + syncerConfig.Contexts[syncerConfig.CurrentContext] = tmpCtx + if k != syncerConfig.CurrentContext { + delete(syncerConfig.Contexts, k) } break } } // check if we need to write the kubeconfig secrete to the default location as well - if options.KubeConfigSecret != "" { + if options.Config.ExportKubeConfig.Secret.Name != "" { // which namespace should we create the additional secret in? - secretNamespace := options.KubeConfigSecretNamespace + secretNamespace := options.Config.ExportKubeConfig.Secret.Namespace if secretNamespace == "" { secretNamespace = currentNamespace } // write the extra secret - err = kubeconfig.WriteKubeConfig(ctx, currentNamespaceClient, options.KubeConfigSecret, secretNamespace, config, isRemote) + err = kubeconfig.WriteKubeConfig(ctx, currentNamespaceClient, options.Config.ExportKubeConfig.Secret.Name, secretNamespace, syncerConfig, options.Config.Experimental.IsolatedControlPlane.KubeConfig != "") if err != nil { - return fmt.Errorf("creating %s secret in the %s ns failed: %w", options.KubeConfigSecret, secretNamespace, err) + return fmt.Errorf("creating %s secret in the %s ns failed: %w", options.Config.ExportKubeConfig.Secret.Name, secretNamespace, err) } } // write the default Secret - return kubeconfig.WriteKubeConfig(ctx, currentNamespaceClient, kubeconfig.GetDefaultSecretName(translate.VClusterName), currentNamespace, config, isRemote) + return kubeconfig.WriteKubeConfig(ctx, currentNamespaceClient, kubeconfig.GetDefaultSecretName(translate.VClusterName), currentNamespace, syncerConfig, options.Config.Experimental.IsolatedControlPlane.KubeConfig != "") } diff --git a/pkg/setup/enable_controllers.go b/pkg/setup/enable_controllers.go deleted file mode 100644 index d8b4fc598..000000000 --- a/pkg/setup/enable_controllers.go +++ /dev/null @@ -1,140 +0,0 @@ -package setup - -import ( - "fmt" - "strings" - - "github.com/loft-sh/vcluster/pkg/constants" - "github.com/loft-sh/vcluster/pkg/options" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/discovery" - "k8s.io/klog/v2" -) - -const ( - storageV1GroupVersion = "storage.k8s.io/v1" -) - -// map from groupversion to list of resources in that groupversion -// the syncers will be disabled unless that resource is advertised in that groupversion -var possibleMissing = map[string][]string{ - storageV1GroupVersion: constants.SchedulerRequiredControllers.UnsortedList(), -} - -func ParseControllers(options *options.VirtualClusterOptions) (sets.Set[string], error) { - enabledControllers := constants.DefaultEnabledControllers.Clone() - disabledControllers := sets.New[string]() - - // migrate deprecated flags - if len(options.DeprecatedDisableSyncResources) > 0 { - disabledControllers.Insert(strings.Split(options.DeprecatedDisableSyncResources, ",")...) - } - if options.DeprecatedEnablePriorityClasses { - enabledControllers.Insert("priorityclasses") - } - if !options.DeprecatedUseFakePersistentVolumes { - enabledControllers.Insert("persistentvolumes") - } - if !options.DeprecatedUseFakeNodes { - enabledControllers.Insert("nodes") - } - if options.DeprecatedEnableStorageClasses { - enabledControllers.Insert("storageclasses") - } - - for _, c := range options.Controllers { - controller := strings.TrimSpace(c) - if len(controller) == 0 { - return nil, fmt.Errorf("unrecognized controller %s, available controllers: %s", c, availableControllers()) - } - - if controller[0] == '-' { - controller = controller[1:] - disabledControllers.Insert(controller) - } else { - enabledControllers.Insert(controller) - } - - if !constants.ExistingControllers.Has(controller) { - return nil, fmt.Errorf("unrecognized controller %s, available controllers: %s", controller, availableControllers()) - } - } - // enable ingressclasses if ingress syncing is enabled and incressclasses not explicitly disabled - if enabledControllers.Has("ingresses") && !disabledControllers.Has("ingressesclasses") { - enabledControllers.Insert("ingressclasses") - } - - // enable namespaces controller in MultiNamespaceMode - if options.MultiNamespaceMode { - enabledControllers.Insert("namespaces") - } - - // do validations on dynamically added controllers here (to take into acount disabledControllers): - - // enable additional controllers required for scheduling with storage - if options.EnableScheduler && enabledControllers.Has("persistentvolumeclaims") { - klog.Infof("persistentvolumeclaim syncing and scheduler enabled, enabling required controllers: %q", constants.SchedulerRequiredControllers) - enabledControllers = enabledControllers.Union(constants.SchedulerRequiredControllers) - requiredButDisabled := disabledControllers.Intersection(constants.SchedulerRequiredControllers) - if requiredButDisabled.Len() > 0 { - klog.Warningf("persistentvolumeclaim syncing and scheduler enabled, but required syncers explicitly disabled: %q. This may result in incorrect pod scheduling.", sets.List(requiredButDisabled)) - } - if !enabledControllers.Has("storageclasses") { - klog.Info("persistentvolumeclaim syncing and scheduler enabled, but storageclass sync not enabled. Syncing host storageclasses to vcluster(hoststorageclasses)") - enabledControllers.Insert("hoststorageclasses") - if disabledControllers.HasAll("storageclasses", "hoststorageclasses") { - return nil, fmt.Errorf("persistentvolumeclaim syncing and scheduler enabled, but both storageclasses and hoststorageclasses syncers disabled") - } - } - } - - // remove explicitly disabled controllers - enabledControllers = enabledControllers.Difference(disabledControllers) - - // do validations on user configured controllers here (on just enabledControllers): - - // check if nodes controller needs to be enabled - if (options.SyncAllNodes || options.EnableScheduler) && !enabledControllers.Has("nodes") { - return nil, fmt.Errorf("node sync needs to be enabled when using --sync-all-nodes OR --enable-scheduler flags") - } - - // check if storage classes and host storage classes are enabled at the same time - if enabledControllers.HasAll("storageclasses", "hoststorageclasses") { - return nil, fmt.Errorf("you cannot sync storageclasses and hoststorageclasses at the same time. Choose only one of them") - } - - return enabledControllers, nil -} - -func availableControllers() string { - return strings.Join(sets.List(constants.ExistingControllers), ", ") -} - -// DisableMissingAPIs checks if the apis are enabled, if any are missing, disable the syncer and print a log -func DisableMissingAPIs(discoveryClient discovery.DiscoveryInterface, controllers sets.Set[string]) (sets.Set[string], error) { - enabledControllers := controllers.Clone() - for groupVersion, resourceList := range possibleMissing { - resources, err := discoveryClient.ServerResourcesForGroupVersion(groupVersion) - if err != nil && !kerrors.IsNotFound(err) { - return nil, err - } - for _, resourcePlural := range resourceList { - found := false - // search the resourses for a match - if resources != nil { - for _, r := range resources.APIResources { - if r.Name == resourcePlural { - found = true - break - } - } - } - if !found { - enabledControllers.Delete(resourcePlural) - klog.Warningf("host kubernetes apiserver not advertising resource %q in GroupVersion %q, disabling the syncer", resourcePlural, storageV1GroupVersion) - } - } - } - return enabledControllers, nil -} diff --git a/pkg/setup/enable_controllers_test.go b/pkg/setup/enable_controllers_test.go deleted file mode 100644 index bf971a64e..000000000 --- a/pkg/setup/enable_controllers_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package setup - -import ( - "testing" - - "github.com/loft-sh/vcluster/pkg/constants" - "github.com/loft-sh/vcluster/pkg/options" - "github.com/spf13/pflag" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - fakeDiscovery "k8s.io/client-go/discovery/fake" - clientTesting "k8s.io/client-go/testing" -) - -func TestEnableControllers(t *testing.T) { - testTable := []struct { - optsModifier func(*options.VirtualClusterOptions) - desc string - errSubString string - expectEnabled []string - expectDisabled []string - expectError bool - pause bool - }{ - { - desc: "default case", - optsModifier: func(_ *options.VirtualClusterOptions) {}, - expectEnabled: sets.List(constants.DefaultEnabledControllers), - expectDisabled: sets.List(constants.ExistingControllers.Difference(constants.DefaultEnabledControllers)), - expectError: false, - }, - { - desc: "scheduler with pvc enabled, nodes not enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"persistentvolumeclaims"} - v.EnableScheduler = true - }, - expectError: true, - }, - { - desc: "scheduler with pvc enabled, storageclasses not enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"persistentvolumeclaims", "nodes"} - v.EnableScheduler = true - }, - expectEnabled: append([]string{"hoststorageclasses"}, sets.List(constants.SchedulerRequiredControllers)...), - expectDisabled: []string{"storageclasses"}, - expectError: false, - }, - { - desc: "scheduler with pvc enabled, storageclasses enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"persistentvolumeclaims", "nodes", "storageclasses"} - v.EnableScheduler = true - }, - expectEnabled: append([]string{"storageclasses"}, sets.List(constants.SchedulerRequiredControllers)...), - expectDisabled: []string{"hoststorageclasses"}, - expectError: false, - }, - { - desc: "scheduler with pvc enabled, hoststorageclasses enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"persistentvolumeclaims", "nodes"} - v.EnableScheduler = true - }, - expectEnabled: append([]string{"hoststorageclasses"}, sets.List(constants.SchedulerRequiredControllers)...), - expectDisabled: []string{"storageclasses"}, - expectError: false, - }, - { - desc: "scheduler disabled, storageclasses not enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"persistentvolumeclaims"} - }, - expectEnabled: []string{}, - expectDisabled: append([]string{"storageclasses", "hoststorageclasses"}, sets.List(constants.SchedulerRequiredControllers)...), - expectError: false, - }, - { - desc: "storageclasses and hoststorageclasses enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"storageclasses", "hoststorageclasses"} - }, - expectEnabled: []string{}, - expectDisabled: []string{}, - expectError: true, - }, - { - desc: "syncAllNodes true, nodes not enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{} - v.SyncAllNodes = true - }, - expectEnabled: []string{}, - expectDisabled: []string{}, - expectError: true, - pause: true, - }, - { - desc: "syncAllNodes true, nodes enabled", - optsModifier: func(v *options.VirtualClusterOptions) { - v.Controllers = []string{"nodes"} - v.SyncAllNodes = true - }, - expectEnabled: []string{}, - expectDisabled: []string{}, - expectError: false, - }, - } - - for _, tc := range testTable { - if tc.pause { - t.Log("you can put a breakpoint here") - } - var opts options.VirtualClusterOptions - options.AddFlags(pflag.NewFlagSet("test", pflag.PanicOnError), &opts) - t.Logf("test case: %q", tc.desc) - if tc.optsModifier != nil { - tc.optsModifier(&opts) - } - - foundControllers, err := ParseControllers(&opts) - if tc.expectError { - assert.ErrorContains(t, err, tc.errSubString, "should have failed validation") - } else { - assert.NilError(t, err, "should have passed validation") - } - - expectedNotFound := sets.New(tc.expectEnabled...).Difference(foundControllers) - assert.Assert(t, is.Len(sets.List(expectedNotFound), 0), "should be enabled, but not enabled") - - disabledFound := sets.New(tc.expectDisabled...).Intersection(foundControllers) - assert.Assert(t, is.Len(sets.List(disabledFound), 0), "should be disabled, but found enabled") - } -} - -var csiStorageCapacityV1 = metav1.APIResource{ - Name: "csistoragecapacities", - SingularName: "csistoragecapacity", - Namespaced: false, - Group: "storage.k8s.io", - Version: "v1", - Kind: "CSIStorageCapacity", -} - -var csiDriverV1 = metav1.APIResource{ - Name: "csidrivers", - SingularName: "csidriver", - Namespaced: false, - Group: "storage.k8s.io", - Version: "v1", - Kind: "CSIDriver", -} - -var csiNodeV1 = metav1.APIResource{ - Name: "csinodes", - SingularName: "csinode", - Namespaced: false, - Group: "storage.k8s.io", - Version: "v1", - Kind: "CSINode", -} - -func TestDisableMissingAPIs(t *testing.T) { - tests := []struct { - apis map[string][]metav1.APIResource - expectedNotFound sets.Set[string] - expectedFound sets.Set[string] - name string - }{ - { - name: "K8s 1.21 or lower", - apis: map[string][]metav1.APIResource{}, - expectedNotFound: constants.SchedulerRequiredControllers, - expectedFound: sets.New[string](), - }, - { - name: "K8s 1.23 or lower", - apis: map[string][]metav1.APIResource{ - storageV1GroupVersion: {csiNodeV1, csiDriverV1}, - }, - expectedNotFound: sets.New("csistoragecapacities"), - expectedFound: sets.New("csinodes", "csidrivers"), - }, - { - name: "K8s 1.24 or higher", - apis: map[string][]metav1.APIResource{ - storageV1GroupVersion: {csiNodeV1, csiDriverV1, csiStorageCapacityV1}, - }, - expectedNotFound: sets.New[string](), - expectedFound: sets.New("csistoragecapacities", "csinodes", "csidrivers"), - }, - } - - for i, testCase := range tests { - t.Logf("running test #%d: %q", i, testCase.name) - // initialize mocked discovery - resourceLists := []*metav1.APIResourceList{} - for groupVersion, resourceList := range testCase.apis { - resourceLists = append(resourceLists, &metav1.APIResourceList{GroupVersion: groupVersion, APIResources: resourceList}) - } - fakeDisoveryClient := &fakeDiscovery.FakeDiscovery{Fake: &clientTesting.Fake{Resources: resourceLists}} - - // run function - actualControllers, err := DisableMissingAPIs(fakeDisoveryClient, constants.ExistingControllers.Clone()) - assert.NilError(t, err) - - // unexpectedly not disabled - notDisabled := actualControllers.Intersection(testCase.expectedNotFound).UnsortedList() - assert.Assert(t, is.Len(notDisabled, 0), "expected %q to be disabled", testCase.expectedNotFound.UnsortedList()) - - // should be enabled - missing := testCase.expectedFound.Difference(actualControllers).UnsortedList() - assert.Assert(t, is.Len(missing, 0), "expected %q to be found, but found only: %q", testCase.expectedFound.UnsortedList(), actualControllers.Intersection(testCase.expectedFound).UnsortedList()) - } -} diff --git a/pkg/setup/initialize.go b/pkg/setup/initialize.go index 7d3768c71..b8774a9d8 100644 --- a/pkg/setup/initialize.go +++ b/pkg/setup/initialize.go @@ -8,14 +8,15 @@ import ( "path/filepath" "reflect" "strconv" + "strings" "time" + vclusterconfig "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/certs" - "github.com/loft-sh/vcluster/pkg/constants" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/k0s" "github.com/loft-sh/vcluster/pkg/k3s" "github.com/loft-sh/vcluster/pkg/k8s" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/pro" "github.com/loft-sh/vcluster/pkg/specialservices" "github.com/loft-sh/vcluster/pkg/telemetry" @@ -31,21 +32,17 @@ import ( // Initialize creates the required secrets and configmaps for the control plane to start func Initialize( ctx context.Context, - workspaceNamespaceClient, currentNamespaceClient kubernetes.Interface, - workspaceNamespace, currentNamespace, vClusterName string, - options *options.VirtualClusterOptions, + options *config.VirtualClusterConfig, ) error { // Ensure that service CIDR range is written into the expected location err := wait.PollUntilContextTimeout(ctx, 5*time.Second, 2*time.Minute, true, func(waitCtx context.Context) (bool, error) { err := initialize( waitCtx, ctx, - workspaceNamespaceClient, currentNamespaceClient, - workspaceNamespace, currentNamespace, vClusterName, options, @@ -70,50 +67,53 @@ func Initialize( func initialize( ctx context.Context, parentCtx context.Context, - workspaceNamespaceClient, currentNamespaceClient kubernetes.Interface, - workspaceNamespace, currentNamespace, vClusterName string, - options *options.VirtualClusterOptions, + options *config.VirtualClusterConfig, ) error { - distro := constants.GetVClusterDistro() + distro := options.Distro() + + // migrate from + migrateFrom := "" + if options.ControlPlane.BackingStore.EmbeddedEtcd.Enabled && options.ControlPlane.BackingStore.EmbeddedEtcd.MigrateFromExternalEtcd { + migrateFrom = "https://" + options.Name + "-etcd:2379" + } // retrieve service cidr - var serviceCIDR string - if distro != constants.K0SDistro { - var warning string - serviceCIDR, warning = servicecidr.GetServiceCIDR(ctx, currentNamespaceClient, currentNamespace) - if warning != "" { - klog.Warning(warning) - } + serviceCIDR, warning := servicecidr.GetServiceCIDR(ctx, currentNamespaceClient, currentNamespace) + if warning != "" { + klog.Warning(warning) } // check what distro are we running switch distro { - case constants.K0SDistro: + case vclusterconfig.K0SDistro: + // only return the first cidr, because k0s don't accept coma separated ones + serviceCIDR = strings.Split(serviceCIDR, ",")[0] + // ensure service cidr - serviceCIDR, err := servicecidr.EnsureServiceCIDRInK0sSecret(ctx, workspaceNamespaceClient, currentNamespaceClient, workspaceNamespace, currentNamespace, vClusterName) + err := k0s.WriteK0sConfig(serviceCIDR, options) if err != nil { return err } // create certificates if they are not there yet certificatesDir := "/data/k0s/pki" - err = GenerateCertsWithEtcdSans(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.ClusterDomain) + err = GenerateCertsWithEtcdSans(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.Networking.Advanced.ClusterDomain) if err != nil { return err } // should start embedded etcd? - if options.ProOptions.EtcdEmbedded && options.ProOptions.EtcdReplicas > 0 { + if options.ControlPlane.BackingStore.EmbeddedEtcd.Enabled { err = pro.StartEmbeddedEtcd( parentCtx, vClusterName, currentNamespace, certificatesDir, - options.ProOptions.EtcdReplicas, - options.ProOptions.MigrateFrom, + int(options.ControlPlane.StatefulSet.HighAvailability.Replicas), + migrateFrom, ) if err != nil { return fmt.Errorf("start embedded etcd: %w", err) @@ -125,7 +125,7 @@ func initialize( go func() { // we need to run this with the parent ctx as otherwise this context will be cancelled by the wait // loop in Initialize - err := k0s.StartK0S(parentCtxWithCancel, cancel) + err := k0s.StartK0S(parentCtxWithCancel, cancel, options) if err != nil { klog.Fatalf("Error running k0s: %v", err) } @@ -137,18 +137,18 @@ func initialize( cancel() return err } - case constants.K3SDistro: + case vclusterconfig.K3SDistro: // its k3s, let's create the token secret - k3sToken, err := k3s.EnsureK3SToken(ctx, currentNamespaceClient, currentNamespace, vClusterName) + k3sToken, err := k3s.EnsureK3SToken(ctx, currentNamespaceClient, currentNamespace, vClusterName, options) if err != nil { return err } // should start embedded etcd? - if options.ProOptions.EtcdEmbedded && options.ProOptions.EtcdReplicas > 0 { + if options.ControlPlane.BackingStore.EmbeddedEtcd.Enabled { // generate certificates certificatesDir := "/data/pki" - err := GenerateCertsWithEtcdSans(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.ClusterDomain) + err := GenerateCertsWithEtcdSans(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.Networking.Advanced.ClusterDomain) if err != nil { return err } @@ -160,8 +160,8 @@ func initialize( vClusterName, currentNamespace, certificatesDir, - options.ProOptions.EtcdReplicas, - options.ProOptions.MigrateFrom, + int(options.ControlPlane.StatefulSet.HighAvailability.Replicas), + migrateFrom, ) if err != nil { return fmt.Errorf("start embedded etcd: %w", err) @@ -172,31 +172,31 @@ func initialize( go func() { // we need to run this with the parent ctx as otherwise this context will be cancelled by the wait // loop in Initialize - err := k3s.StartK3S(parentCtx, serviceCIDR, k3sToken) + err := k3s.StartK3S(parentCtx, options, serviceCIDR, k3sToken) if err != nil { klog.Fatalf("Error running k3s: %v", err) } }() - case constants.K8SDistro, constants.EKSDistro: + case vclusterconfig.K8SDistro, vclusterconfig.EKSDistro: // try to generate k8s certificates - certificatesDir := filepath.Dir(options.ServerCaCert) + certificatesDir := filepath.Dir(options.VirtualClusterKubeConfig().ServerCACert) if certificatesDir == "/pki" { - err := GenerateK8sCerts(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.ClusterDomain) + err := GenerateK8sCerts(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.Networking.Advanced.ClusterDomain) if err != nil { return err } } // should start embedded etcd? - if options.ProOptions.EtcdEmbedded && options.ProOptions.EtcdReplicas > 0 { + if options.ControlPlane.BackingStore.EmbeddedEtcd.Enabled { // start embedded etcd err := pro.StartEmbeddedEtcd( parentCtx, vClusterName, currentNamespace, certificatesDir, - options.ProOptions.EtcdReplicas, - options.ProOptions.MigrateFrom, + int(options.ControlPlane.StatefulSet.HighAvailability.Replicas), + migrateFrom, ) if err != nil { return fmt.Errorf("start embedded etcd: %w", err) @@ -207,16 +207,35 @@ func initialize( go func() { // we need to run this with the parent ctx as otherwise this context will be cancelled by the wait // loop in Initialize - err := k8s.StartK8S(parentCtx, serviceCIDR) + var err error + if distro == vclusterconfig.K8SDistro { + err = k8s.StartK8S( + parentCtx, + serviceCIDR, + options.ControlPlane.Distro.K8S.APIServer, + options.ControlPlane.Distro.K8S.ControllerManager, + options.ControlPlane.Distro.K8S.Scheduler, + options, + ) + } else if distro == vclusterconfig.EKSDistro { + err = k8s.StartK8S( + parentCtx, + serviceCIDR, + options.ControlPlane.Distro.EKS.APIServer, + options.ControlPlane.Distro.EKS.ControllerManager, + options.ControlPlane.Distro.EKS.Scheduler, + options, + ) + } if err != nil { klog.Fatalf("Error running k8s: %v", err) } }() - case constants.Unknown: - certificatesDir := filepath.Dir(options.ServerCaCert) + case vclusterconfig.Unknown: + certificatesDir := filepath.Dir(options.VirtualClusterKubeConfig().ServerCACert) if certificatesDir == "/pki" { // generate k8s certificates - err := GenerateK8sCerts(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.ClusterDomain) + err := GenerateK8sCerts(ctx, currentNamespaceClient, vClusterName, currentNamespace, serviceCIDR, certificatesDir, options.Networking.Advanced.ClusterDomain) if err != nil { return err } diff --git a/pkg/setup/proxy.go b/pkg/setup/proxy.go index 2cb729362..fc2cdcc94 100644 --- a/pkg/setup/proxy.go +++ b/pkg/setup/proxy.go @@ -1,14 +1,14 @@ package setup import ( - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/pro" "github.com/loft-sh/vcluster/pkg/server" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" ) -func StartProxy(ctx *options.ControllerContext, controlPlaneNamespace, controlPlaneService string, controlPlaneClient kubernetes.Interface) error { +func StartProxy(ctx *config.ControllerContext, controlPlaneNamespace, controlPlaneService string, controlPlaneClient kubernetes.Interface) error { // add remote node port sans err := pro.AddRemoteNodePortSANs(ctx.Context, controlPlaneNamespace, controlPlaneService, controlPlaneClient) if err != nil { @@ -16,14 +16,14 @@ func StartProxy(ctx *options.ControllerContext, controlPlaneNamespace, controlPl } // start the proxy - proxyServer, err := server.NewServer(ctx, ctx.Options.RequestHeaderCaCert, ctx.Options.ClientCaCert) + proxyServer, err := server.NewServer(ctx, ctx.Config.VirtualClusterKubeConfig().RequestHeaderCACert, ctx.Config.VirtualClusterKubeConfig().ClientCACert) if err != nil { return err } // start the proxy server in secure mode go func() { - err = proxyServer.ServeOnListenerTLS(ctx.Options.BindAddress, ctx.Options.Port, ctx.StopChan) + err = proxyServer.ServeOnListenerTLS(ctx.Config.ControlPlane.Proxy.BindAddress, ctx.Config.ControlPlane.Proxy.Port, ctx.StopChan) if err != nil { klog.Fatalf("Error serving: %v", err) } diff --git a/pkg/telemetry/collect.go b/pkg/telemetry/collect.go index 16251a72a..572e77234 100644 --- a/pkg/telemetry/collect.go +++ b/pkg/telemetry/collect.go @@ -10,7 +10,7 @@ import ( "github.com/loft-sh/analytics-client/client" managementv1 "github.com/loft-sh/api/v3/pkg/apis/management/v1" "github.com/loft-sh/log" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/upgrade" "github.com/loft-sh/vcluster/pkg/util/cliconfig" "github.com/loft-sh/vcluster/pkg/util/clihelper" @@ -45,7 +45,7 @@ type EventCollector interface { // Flush makes sure all events are sent to the backend Flush() - Init(currentNamespaceConfig *rest.Config, currentNamespace string, options *options.VirtualClusterOptions) + Init(currentNamespaceConfig *rest.Config, currentNamespace string, vConfig *config.VirtualClusterConfig) SetVirtualClient(virtualClient *kubernetes.Clientset) } @@ -110,7 +110,7 @@ type DefaultCollector struct { // everything below will be set during runtime virtualClient *kubernetes.Clientset - options *options.VirtualClusterOptions + options *config.VirtualClusterConfig hostClient *kubernetes.Clientset hostNamespace string } @@ -123,7 +123,7 @@ func (d *DefaultCollector) startReportStatus(ctx context.Context) { }, time.Minute*5, ctx.Done()) } -func (d *DefaultCollector) Init(currentNamespaceConfig *rest.Config, currentNamespace string, options *options.VirtualClusterOptions) { +func (d *DefaultCollector) Init(currentNamespaceConfig *rest.Config, currentNamespace string, vConfig *config.VirtualClusterConfig) { hostClient, err := kubernetes.NewForConfig(currentNamespaceConfig) if err != nil { klog.V(1).ErrorS(err, "create host client") @@ -131,7 +131,7 @@ func (d *DefaultCollector) Init(currentNamespaceConfig *rest.Config, currentName d.hostClient = hostClient d.hostNamespace = currentNamespace - d.options = options + d.options = vConfig } func (d *DefaultCollector) SetVirtualClient(virtualClient *kubernetes.Clientset) { diff --git a/pkg/telemetry/helpers.go b/pkg/telemetry/helpers.go index 89c6293e7..78cc00446 100644 --- a/pkg/telemetry/helpers.go +++ b/pkg/telemetry/helpers.go @@ -9,8 +9,8 @@ import ( "github.com/denisbrodbeck/machineid" managementv1 "github.com/loft-sh/api/v3/pkg/apis/management/v1" "github.com/loft-sh/log" + "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/helm" - "github.com/loft-sh/vcluster/pkg/options" "github.com/loft-sh/vcluster/pkg/util/cliconfig" "github.com/loft-sh/vcluster/pkg/util/translate" homedir "github.com/mitchellh/go-homedir" @@ -58,7 +58,7 @@ func getChartInfo(ctx context.Context, hostClient *kubernetes.Clientset, vCluste } // getVClusterID provides instance ID based on the UID of the service -func getVClusterID(ctx context.Context, hostClient *kubernetes.Clientset, vClusterNamespace string, options *options.VirtualClusterOptions) (string, error) { +func getVClusterID(ctx context.Context, hostClient *kubernetes.Clientset, vClusterNamespace string, options *config.VirtualClusterConfig) (string, error) { if hostClient == nil || options == nil { return "", fmt.Errorf("kubernetes client or options are nil") } @@ -72,7 +72,7 @@ func getVClusterID(ctx context.Context, hostClient *kubernetes.Clientset, vClust } // returns a Kubernetes resource that can be used to uniquely identify this syncer instance - PVC or Service -func getUniqueSyncerObject(ctx context.Context, c *kubernetes.Clientset, vClusterNamespace string, options *options.VirtualClusterOptions) (client.Object, error) { +func getUniqueSyncerObject(ctx context.Context, c *kubernetes.Clientset, vClusterNamespace string, options *config.VirtualClusterConfig) (client.Object, error) { // If vCluster PVC doesn't exist we try to get UID from the vCluster Service if options.ServiceName == "" { return nil, fmt.Errorf("getUniqueSyncerObject failed - options.ServiceName is empty") diff --git a/pkg/telemetry/noop.go b/pkg/telemetry/noop.go index 84382b5be..eab903d1f 100644 --- a/pkg/telemetry/noop.go +++ b/pkg/telemetry/noop.go @@ -4,7 +4,7 @@ import ( "context" managementv1 "github.com/loft-sh/api/v3/pkg/apis/management/v1" - "github.com/loft-sh/vcluster/pkg/options" + "github.com/loft-sh/vcluster/pkg/config" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -15,7 +15,7 @@ func (n *noopCollector) RecordStart(_ context.Context) {} func (n *noopCollector) RecordError(_ context.Context, _ ErrorSeverityType, _ error) {} -func (n *noopCollector) Init(_ *rest.Config, _ string, _ *options.VirtualClusterOptions) {} +func (n *noopCollector) Init(_ *rest.Config, _ string, _ *config.VirtualClusterConfig) {} func (n *noopCollector) Flush() {} diff --git a/pkg/util/context/converter.go b/pkg/util/context/converter.go index 9f6922361..61ee5e0e7 100644 --- a/pkg/util/context/converter.go +++ b/pkg/util/context/converter.go @@ -1,16 +1,15 @@ package util import ( + "github.com/loft-sh/vcluster/pkg/config" synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context" - "github.com/loft-sh/vcluster/pkg/options" ) -func ToRegisterContext(ctx *options.ControllerContext) *synccontext.RegisterContext { +func ToRegisterContext(ctx *config.ControllerContext) *synccontext.RegisterContext { return &synccontext.RegisterContext{ Context: ctx.Context, - Options: ctx.Options, - Controllers: ctx.Controllers, + Config: ctx.Config, CurrentNamespace: ctx.CurrentNamespace, CurrentNamespaceClient: ctx.CurrentNamespaceClient, diff --git a/pkg/util/servicecidr/servicecidr.go b/pkg/util/servicecidr/servicecidr.go index 28bd3555c..2caab4e82 100644 --- a/pkg/util/servicecidr/servicecidr.go +++ b/pkg/util/servicecidr/servicecidr.go @@ -4,69 +4,18 @@ import ( "context" "fmt" "net" - "os" "strings" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/klog/v2" ) const ( - CIDRConfigMapPrefix = "vc-cidr-" - CIDRConfigMapKey = "cidr" - K0sConfigKey = "config.yaml" - K0sCIDRPlaceHolder = "CIDR_PLACEHOLDER" - ErrorMessageFind = "The range of valid IPs is " FallbackCIDR = "10.96.0.0/12" ) -func GetK0sSecretName(vClusterName string) string { - return fmt.Sprintf("vc-%s-config", vClusterName) -} - -func EnsureServiceCIDRInK0sSecret( - ctx context.Context, - workspaceNamespaceClient, - currentNamespaceClient kubernetes.Interface, - workspaceNamespace, - currentNamespace string, - vClusterName string, -) (string, error) { - secret, err := currentNamespaceClient.CoreV1().Secrets(currentNamespace).Get(ctx, GetK0sSecretName(vClusterName), metav1.GetOptions{}) - if err != nil { - return "", fmt.Errorf("could not read k0s configuration secret %s/%s: %w", currentNamespace, GetK0sSecretName(vClusterName), err) - } - - // verify secret - configData, ok := secret.Data[K0sConfigKey] - if !ok { - return "", fmt.Errorf("k0s configuration secret %s/%s does not contain the expected key - %s", secret.Namespace, secret.Name, K0sConfigKey) - } - - // find out correct cidr - serviceCIDR, warning := GetServiceCIDR(ctx, workspaceNamespaceClient, workspaceNamespace) - if warning != "" { - klog.Info(warning) - } - // only return the first cidr, because k0s don't accept coma separated ones - serviceCIDR = strings.Split(serviceCIDR, ",")[0] - - // apply changes - updatedConfig := []byte(strings.ReplaceAll(string(configData), K0sCIDRPlaceHolder, serviceCIDR)) - - // write the config to file - err = os.WriteFile("/tmp/k0s-config.yaml", updatedConfig, 0640) - if err != nil { - klog.Errorf("error while write k0s config to file: %s", err.Error()) - return "", err - } - - return serviceCIDR, nil -} - func GetServiceCIDR(ctx context.Context, client kubernetes.Interface, namespace string) (string, string) { ipv4CIDR, ipv4Err := getServiceCIDR(ctx, client, namespace, false) ipv6CIDR, ipv6Err := getServiceCIDR(ctx, client, namespace, true) diff --git a/pkg/util/translate/lb_service_name.go b/pkg/util/translate/lb_service_name.go deleted file mode 100644 index f0dc1e52b..000000000 --- a/pkg/util/translate/lb_service_name.go +++ /dev/null @@ -1,9 +0,0 @@ -package translate - -import "fmt" - -// GetLoadBalancerSVCName retrieves the service name if service name is set to type LoadBalancer. -// A separate service is created in this case so as to expose only the apiserver and not the kubelet port -func GetLoadBalancerSVCName(serviceName string) string { - return fmt.Sprintf("%s-lb", serviceName) -} diff --git a/test/commonValues.yaml b/test/commonValues.yaml index e17fdf15b..fc573d67a 100644 --- a/test/commonValues.yaml +++ b/test/commonValues.yaml @@ -1,70 +1,43 @@ -syncer: - extraArgs: - - --service-account-token-secrets=true - image: REPLACE_IMAGE_NAME - env: - - name: DEBUG - value: "true" - resources: - requests: - cpu: "0" - -vcluster: - resources: - requests: - cpu: "0" - -etcd: - resources: - requests: - cpu: "0" +sync: + toHost: + pods: + useSecretsForSATokens: true + fromHost: + nodes: + enabled: true + selector: + labels: + kubernetes.io/hostname: "kind-control-plane" -controller: - resources: - requests: - cpu: "0" +controlPlane: + backingStore: + externalEtcd: + statefulSet: + resources: + requests: + cpu: "0" -api: - resources: - requests: - cpu: "0" + statefulSet: + image: + repository: REPLACE_REPOSITORY_NAME + tag: REPLACE_TAG_NAME + env: + - name: DEBUG + value: "true" + resources: + requests: + cpu: "0" # values for general test suite -mapServices: - fromVirtual: - - from: test/test - to: test - - from: test/nginx - to: nginx - fromHost: - - from: test/test - to: default/test - - from: test/nginx - to: default/nginx - -init: - helm: - - chart: - name: ingress-nginx - repo: https://kubernetes.github.io/ingress-nginx - version: 4.1.1 - release: - name: ingress-nginx - namespace: ingress-nginx - timeout: "50s" - - chart: - name: fluent-bit - repo: oci://registry-1.docker.io/bitnamicharts - version: 0.4.3 - release: - name: fluent-bit - namespace: fluent-bit - timeout: "50s" - -sync: - pods: - ephemeralContainers: true - status: true - nodes: - enabled: true - nodeSelector: "kubernetes.io/hostname=kind-control-plane" +networking: + replicateServices: + toHost: + - from: test/test + to: test + - from: test/nginx + to: nginx + fromHost: + - from: test/test + to: default/test + - from: test/nginx + to: default/nginx diff --git a/test/e2e/manifests/chart.go b/test/e2e/manifests/chart.go index 3534e9112..cf179ae21 100644 --- a/test/e2e/manifests/chart.go +++ b/test/e2e/manifests/chart.go @@ -2,10 +2,9 @@ package manifests import ( "context" - "fmt" "time" - "github.com/loft-sh/vcluster/pkg/controllers/manifests" + "github.com/loft-sh/vcluster/pkg/controllers/deploy" "github.com/loft-sh/vcluster/test/framework" "github.com/onsi/ginkgo/v2" @@ -25,9 +24,8 @@ const ( var _ = ginkgo.Describe("Helm charts (regular and OCI) are synced and applied as expected", func() { var ( - f *framework.Framework - hostConfigMapName string - HelmSecretLabels = map[string]string{ + f = framework.DefaultFramework + HelmSecretLabels = map[string]string{ "owner": "helm", "name": ChartName, } @@ -37,23 +35,10 @@ var _ = ginkgo.Describe("Helm charts (regular and OCI) are synced and applied as } ) - ginkgo.JustBeforeEach(func() { - f = framework.DefaultFramework - hostConfigMapName = fmt.Sprintf("%s-%s", f.VclusterNamespace, InitConfigmapSuffix) - }) - - ginkgo.It("Test if configmap for both charts is created as expected", func() { - _, err := f.HostClient. - CoreV1(). - ConfigMaps(f.VclusterNamespace). - Get(f.Context, hostConfigMapName, metav1.GetOptions{}) - framework.ExpectNoError(err) - }) - ginkgo.It("Test if configmap for both charts gets applied", func() { err := wait.PollUntilContextTimeout(f.Context, time.Millisecond*500, framework.PollTimeout, true, func(ctx context.Context) (bool, error) { - cm, err := f.HostClient.CoreV1().ConfigMaps(f.VclusterNamespace). - Get(ctx, hostConfigMapName, metav1.GetOptions{}) + cm, err := f.VclusterClient.CoreV1().ConfigMaps(deploy.VClusterDeployConfigMapNamespace). + Get(ctx, deploy.VClusterDeployConfigMap, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { return false, nil @@ -61,15 +46,15 @@ var _ = ginkgo.Describe("Helm charts (regular and OCI) are synced and applied as return false, err } - status := manifests.ParseStatus(cm) // validate that all charts are Success + status := deploy.ParseStatus(cm) for _, chart := range status.Charts { - if chart.Phase != string(manifests.StatusSuccess) { + if chart.Phase != string(deploy.StatusSuccess) { return false, nil } } - return status.Phase == string(manifests.StatusSuccess) && len(status.Charts) == 2, nil + return status.Phase == string(deploy.StatusSuccess) && len(status.Charts) == 2, nil }) framework.ExpectNoError(err) diff --git a/test/e2e/manifests/init.go b/test/e2e/manifests/init.go index d9fdcf00b..a135c633c 100644 --- a/test/e2e/manifests/init.go +++ b/test/e2e/manifests/init.go @@ -1,92 +1,37 @@ package manifests import ( - "fmt" - - "github.com/loft-sh/vcluster/pkg/util/random" "github.com/loft-sh/vcluster/test/framework" "github.com/onsi/ginkgo/v2" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( TestManifestName = "test-configmap" + TestManifestName2 = "test-configmap-2" TestManifestNamespace = "default" - - InitConfigmapSuffix = "init-manifests" ) var _ = ginkgo.Describe("Init manifests are synced and applied as expected", func() { - var ( - f *framework.Framework - iteration int - ns string - ) - - ginkgo.JustBeforeEach(func() { - f = framework.DefaultFramework - iteration++ - ns = fmt.Sprintf("e2e-init-manifests-%d-%s", iteration, random.String(5)) - - _, err := f.VclusterClient.CoreV1().Namespaces().Create(f.Context, &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: ns, - }}, metav1.CreateOptions{}) - framework.ExpectNoError(err) - }) - - ginkgo.AfterEach(func() { - err := f.DeleteTestNamespace(ns, false) - framework.ExpectNoError(err) - - // reset configmap to be empty - initmanifests, err := f.HostClient. - CoreV1(). - ConfigMaps(f.VclusterNamespace). - Get(f.Context, fmt.Sprintf("%s-%s", f.VclusterNamespace, InitConfigmapSuffix), metav1.GetOptions{}) - framework.ExpectNoError(err) - - initmanifests.Data["manifests"] = "---" - _, err = f.HostClient.CoreV1().ConfigMaps(f.VclusterNamespace).Update(f.Context, initmanifests, metav1.UpdateOptions{}) - framework.ExpectNoError(err) - }) - - ginkgo.It("Test if init manifests are initially empty", func() { - initmanifests, err := f.HostClient. - CoreV1(). - ConfigMaps(f.VclusterNamespace). - Get(f.Context, fmt.Sprintf("%s-%s", f.VclusterNamespace, InitConfigmapSuffix), metav1.GetOptions{}) - framework.ExpectNoError(err) - - framework.ExpectEqual(initmanifests.Data["manifests"], "---") - }) + f := framework.DefaultFramework ginkgo.It("Test if manifest is synced with the vcluster", func() { - initmanifests, err := f.HostClient. - CoreV1(). - ConfigMaps(f.VclusterNamespace). - Get(f.Context, fmt.Sprintf("%s-%s", f.VclusterNamespace, InitConfigmapSuffix), metav1.GetOptions{}) + err := f.WaitForInitManifestConfigMapCreation(TestManifestName, TestManifestNamespace) framework.ExpectNoError(err) - testManifest := fmt.Sprintf(`apiVersion: v1 -kind: ConfigMap -metadata: - name: %s -data: - foo: bar -`, TestManifestName) - - initmanifests.Data["manifests"] = testManifest - _, err = f.HostClient.CoreV1().ConfigMaps(f.VclusterNamespace).Update(f.Context, initmanifests, metav1.UpdateOptions{}) + manifest, err := f.VclusterClient.CoreV1().ConfigMaps(TestManifestNamespace).Get(f.Context, TestManifestName, metav1.GetOptions{}) framework.ExpectNoError(err) + framework.ExpectHaveKey(manifest.Data, "foo", "modified init manifest is supposed to have the foo key") + framework.ExpectEqual(manifest.Data["foo"], "bar") + }) - err = f.WaitForInitManifestConfigMapCreation(TestManifestName, TestManifestNamespace) + ginkgo.It("Test if manifest template is synced with the vcluster", func() { + err := f.WaitForInitManifestConfigMapCreation(TestManifestName2, TestManifestNamespace) framework.ExpectNoError(err) - manifest, err := f.VclusterClient.CoreV1().ConfigMaps(TestManifestNamespace).Get(f.Context, TestManifestName, metav1.GetOptions{}) + manifest, err := f.VclusterClient.CoreV1().ConfigMaps(TestManifestNamespace).Get(f.Context, TestManifestName2, metav1.GetOptions{}) framework.ExpectNoError(err) framework.ExpectHaveKey(manifest.Data, "foo", "modified init manifest is supposed to have the foo key") - framework.ExpectEqual(manifest.Data["foo"], "bar") + framework.ExpectEqual(manifest.Data["foo"], "vcluster") }) }) diff --git a/test/e2e/values.yaml b/test/e2e/values.yaml index e77ae2ce2..bcc932e2a 100644 --- a/test/e2e/values.yaml +++ b/test/e2e/values.yaml @@ -1,3 +1,33 @@ -# empty values file to avoid no file or dir error -# this test suite will be run for all configurations -# so add values for this test suite in commonValues.yaml +experimental: + deploy: + manifests: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap + data: + foo: bar + manifestsTemplate: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap-2 + data: + foo: {{ .Release.Name }} + helm: + - chart: + name: ingress-nginx + repo: https://kubernetes.github.io/ingress-nginx + version: 4.1.1 + release: + name: ingress-nginx + namespace: ingress-nginx + timeout: "50s" + - chart: + name: fluent-bit + repo: oci://registry-1.docker.io/bitnamicharts + version: 0.4.3 + release: + name: fluent-bit + namespace: fluent-bit + timeout: "50s" diff --git a/test/e2e_generic/values.yaml b/test/e2e_generic/values.yaml index 61f7ebf83..5f186b230 100644 --- a/test/e2e_generic/values.yaml +++ b/test/e2e_generic/values.yaml @@ -1,7 +1,7 @@ -multiNamespaceMode: - enabled: true -sync: - generic: +experimental: + multiNamespaceMode: + enabled: true + genericSync: role: extraRules: - apiGroups: ["networking.k8s.io"] @@ -12,12 +12,10 @@ sync: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - config: |- - version: v1beta1 - import: - - kind: IngressClass - apiVersion: networking.k8s.io/v1 - # test to ensure optional resources work without crashing - - kind: ClusterIssuer - apiVersion: cert-manager.io/v1 - optional: true \ No newline at end of file + import: + - kind: IngressClass + apiVersion: networking.k8s.io/v1 + # test to ensure optional resources work without crashing + - kind: ClusterIssuer + apiVersion: cert-manager.io/v1 + optional: true diff --git a/test/e2e_isolation_mode/isolated.go b/test/e2e_isolation_mode/isolated.go index 241d46052..24babf060 100644 --- a/test/e2e_isolation_mode/isolated.go +++ b/test/e2e_isolation_mode/isolated.go @@ -22,15 +22,15 @@ var _ = ginkgo.Describe("Isolated mode", func() { ginkgo.It("Enforce isolated mode", func() { ginkgo.By("Check if isolated mode creates resourcequota") - _, err := f.HostClient.CoreV1().ResourceQuotas(f.VclusterNamespace).Get(f.Context, "vcluster-quota", metav1.GetOptions{}) + _, err := f.HostClient.CoreV1().ResourceQuotas(f.VclusterNamespace).Get(f.Context, "vc-vcluster", metav1.GetOptions{}) framework.ExpectNoError(err) ginkgo.By("Check if isolated mode creates limitrange") - _, err = f.HostClient.CoreV1().LimitRanges(f.VclusterNamespace).Get(f.Context, "vcluster-limit-range", metav1.GetOptions{}) + _, err = f.HostClient.CoreV1().LimitRanges(f.VclusterNamespace).Get(f.Context, "vc-vcluster", metav1.GetOptions{}) framework.ExpectNoError(err) ginkgo.By("Check if isolated mode creates networkpolicy") - _, err = f.HostClient.NetworkingV1().NetworkPolicies(f.VclusterNamespace).Get(f.Context, "vcluster-workloads", metav1.GetOptions{}) + _, err = f.HostClient.NetworkingV1().NetworkPolicies(f.VclusterNamespace).Get(f.Context, "vc-work-vcluster", metav1.GetOptions{}) framework.ExpectNoError(err) ginkgo.By("Check if isolated mode applies baseline PodSecurityStandards to namespaces in vcluster") @@ -110,5 +110,4 @@ var _ = ginkgo.Describe("Isolated mode", func() { err = f.VclusterClient.CoreV1().Pods("default").Delete(f.Context, pod.Name, metav1.DeleteOptions{}) framework.ExpectNoError(err) }) - }) diff --git a/test/e2e_isolation_mode/values.yaml b/test/e2e_isolation_mode/values.yaml index 2bb14e329..fc74e29fc 100644 --- a/test/e2e_isolation_mode/values.yaml +++ b/test/e2e_isolation_mode/values.yaml @@ -1,7 +1,47 @@ -isolation: - enabled: true - nodeProxyPermission: - enabled: true +policies: + podSecurityStandard: baseline + resourceQuota: + enabled: true quota: services.nodeports: 3 + + limitRange: + enabled: true + + networkPolicy: + enabled: true + +experimental: + deploy: + manifests: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap + data: + foo: bar + manifestsTemplate: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap-2 + data: + foo: {{ .Release.Name }} + helm: + - chart: + name: ingress-nginx + repo: https://kubernetes.github.io/ingress-nginx + version: 4.1.1 + release: + name: ingress-nginx + namespace: ingress-nginx + timeout: "50s" + - chart: + name: fluent-bit + repo: oci://registry-1.docker.io/bitnamicharts + version: 0.4.3 + release: + name: fluent-bit + namespace: fluent-bit + timeout: "50s" diff --git a/test/e2e_metrics_proxy/values.yaml b/test/e2e_metrics_proxy/values.yaml index c3d366048..811f44fa5 100644 --- a/test/e2e_metrics_proxy/values.yaml +++ b/test/e2e_metrics_proxy/values.yaml @@ -1,6 +1,5 @@ -proxy: - metricsServer: - nodes: - enabled: true - pods: - enabled: true \ No newline at end of file +observability: + metrics: + proxy: + nodes: true + pods: true diff --git a/test/e2e_node/values.yaml b/test/e2e_node/values.yaml index 765705316..d0ba4bf48 100644 --- a/test/e2e_node/values.yaml +++ b/test/e2e_node/values.yaml @@ -1,6 +1,46 @@ +controlPlane: + advanced: + virtualScheduler: + enabled: true + sync: - nodes: - enabled: true - enableScheduler: true - # Either syncAllNodes or nodeSelector is required - syncAllNodes: true + fromHost: + nodes: + enabled: true + # Either syncAllNodes or nodeSelector is required + selector: + all: true + +experimental: + deploy: + manifests: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap + data: + foo: bar + manifestsTemplate: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap-2 + data: + foo: {{ .Release.Name }} + helm: + - chart: + name: ingress-nginx + repo: https://kubernetes.github.io/ingress-nginx + version: 4.1.1 + release: + name: ingress-nginx + namespace: ingress-nginx + timeout: "50s" + - chart: + name: fluent-bit + repo: oci://registry-1.docker.io/bitnamicharts + version: 0.4.3 + release: + name: fluent-bit + namespace: fluent-bit + timeout: "50s" diff --git a/test/e2e_plugin/values.yaml b/test/e2e_plugin/values.yaml index 91236da2a..ebe10a8c7 100644 --- a/test/e2e_plugin/values.yaml +++ b/test/e2e_plugin/values.yaml @@ -8,7 +8,9 @@ plugin: version: v2 image: ghcr.io/loft-sh/vcluster-example-import-secrets:v1 imagePullPolicy: IfNotPresent + +plugins: hooks: - version: v2 image: ghcr.io/loft-sh/vcluster-example-hooks:v1 imagePullPolicy: IfNotPresent + diff --git a/test/e2e_rootless/values.yaml b/test/e2e_rootless/values.yaml index 5da246c08..cbb8b7cee 100644 --- a/test/e2e_rootless/values.yaml +++ b/test/e2e_rootless/values.yaml @@ -1,32 +1,72 @@ -securityContext: - runAsUser: 12345 - runAsNonRoot: true - -fsGroup: 12345 - ## this values are for k8s distro -syncer: - securityContext: - runAsUser: 12345 - runAsNonRoot: true +controlPlane: + backingStore: + externalEtcd: + statefulSet: + security: + podSecurityContext: + fsGroup: 12345 -etcd: - fsGroup: 12345 + statefulSet: + security: + podSecurityContext: + fsGroup: 12345 + containerSecurityContext: + runAsUser: 12345 + runAsNonRoot: true # values for general test suite -mapServices: - fromVirtual: - - from: test/test - to: test - - from: test/nginx - to: nginx - fromHost: - - from: test/test - to: default/test - - from: test/nginx - to: default/nginx +networking: + replicateServices: + toHost: + - from: test/test + to: test + - from: test/nginx + to: nginx + fromHost: + - from: test/test + to: default/test + - from: test/nginx + to: default/nginx sync: + fromHost: nodes: enabled: true - nodeSelector: "kubernetes.io/hostname=kind-control-plane" + selector: + labels: + kubernetes.io/hostname: "kind-control-plane" + +experimental: + deploy: + manifests: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap + data: + foo: bar + manifestsTemplate: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap-2 + data: + foo: {{ .Release.Name }} + helm: + - chart: + name: ingress-nginx + repo: https://kubernetes.github.io/ingress-nginx + version: 4.1.1 + release: + name: ingress-nginx + namespace: ingress-nginx + timeout: "50s" + - chart: + name: fluent-bit + repo: oci://registry-1.docker.io/bitnamicharts + version: 0.4.3 + release: + name: fluent-bit + namespace: fluent-bit + timeout: "50s" diff --git a/test/e2e_scheduler/values.yaml b/test/e2e_scheduler/values.yaml index 765705316..70ad522b9 100644 --- a/test/e2e_scheduler/values.yaml +++ b/test/e2e_scheduler/values.yaml @@ -1,6 +1,46 @@ sync: - nodes: - enabled: true - enableScheduler: true - # Either syncAllNodes or nodeSelector is required - syncAllNodes: true + fromHost: + nodes: + enabled: true + # Either syncAllNodes or nodeSelector is required + selector: + all: true + +controlPlane: + advanced: + virtualScheduler: + enabled: true + +experimental: + deploy: + manifests: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap + data: + foo: bar + manifestsTemplate: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-configmap-2 + data: + foo: {{ .Release.Name }} + helm: + - chart: + name: ingress-nginx + repo: https://kubernetes.github.io/ingress-nginx + version: 4.1.1 + release: + name: ingress-nginx + namespace: ingress-nginx + timeout: "50s" + - chart: + name: fluent-bit + repo: oci://registry-1.docker.io/bitnamicharts + version: 0.4.3 + release: + name: fluent-bit + namespace: fluent-bit + timeout: "50s" diff --git a/test/e2e_target_namespace/values.yaml b/test/e2e_target_namespace/values.yaml index 4ac625c51..1fc473219 100644 --- a/test/e2e_target_namespace/values.yaml +++ b/test/e2e_target_namespace/values.yaml @@ -1,3 +1,3 @@ -syncer: - extraArgs: - - --target-namespace=vcluster-workload +experimental: + syncSettings: + targetNamespace: vcluster-workload diff --git a/test/multins_values.yaml b/test/multins_values.yaml index defbbb066..3962df342 100644 --- a/test/multins_values.yaml +++ b/test/multins_values.yaml @@ -1,2 +1,3 @@ -multiNamespaceMode: - enabled: true \ No newline at end of file +experimental: + multiNamespaceMode: + enabled: true diff --git a/test/values_ha.yaml b/test/values_ha.yaml index b0b6e35af..521a1310e 100644 --- a/test/values_ha.yaml +++ b/test/values_ha.yaml @@ -1,22 +1,17 @@ # this is for k3s pro -replicas: 3 +controlPlane: + statefulSet: + highAvailability: + replicas: 3 -# Scale up syncer replicas -syncer: - replicas: 3 + # Scale up etcd + backingStore: + externalEtcd: + statefulSet: + highAvailability: + replicas: 3 -# Scale up etcd -etcd: - replicas: 3 - -# Scale up controller manager -controller: - replicas: 3 - -# Scale up api server -api: - replicas: 3 - -# Scale up DNS server -coredns: - replicas: 3 + # Scale up DNS server + coredns: + deployment: + replicas: 3 diff --git a/vendor/github.com/bahlo/generic-list-go/LICENSE b/vendor/github.com/bahlo/generic-list-go/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/bahlo/generic-list-go/README.md b/vendor/github.com/bahlo/generic-list-go/README.md new file mode 100644 index 000000000..68bbce9fb --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/README.md @@ -0,0 +1,5 @@ +# generic-list-go [![CI](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml/badge.svg)](https://github.com/bahlo/generic-list-go/actions/workflows/ci.yml) + +Go [container/list](https://pkg.go.dev/container/list) but with generics. + +The code is based on `container/list` in `go1.18beta2`. diff --git a/vendor/github.com/bahlo/generic-list-go/list.go b/vendor/github.com/bahlo/generic-list-go/list.go new file mode 100644 index 000000000..a06a7c612 --- /dev/null +++ b/vendor/github.com/bahlo/generic-list-go/list.go @@ -0,0 +1,235 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package list implements a doubly linked list. +// +// To iterate over a list (where l is a *List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e.Value +// } +// +package list + +// Element is an element of a linked list. +type Element[T any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Element[T] + + // The list to which this element belongs. + list *List[T] + + // The value stored with this element. + Value T +} + +// Next returns the next list element or nil. +func (e *Element[T]) Next() *Element[T] { + if p := e.next; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// Prev returns the previous list element or nil. +func (e *Element[T]) Prev() *Element[T] { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// List represents a doubly linked list. +// The zero value for List is an empty list ready to use. +type List[T any] struct { + root Element[T] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *List[T]) Init() *List[T] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// New returns an initialized list. +func New[T any]() *List[T] { return new(List[T]).Init() } + +// Len returns the number of elements of list l. +// The complexity is O(1). +func (l *List[T]) Len() int { return l.len } + +// Front returns the first element of list l or nil if the list is empty. +func (l *List[T]) Front() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.next +} + +// Back returns the last element of list l or nil if the list is empty. +func (l *List[T]) Back() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *List[T]) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *List[T]) insert(e, at *Element[T]) *Element[T] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { + return l.insert(&Element[T]{Value: v}, at) +} + +// remove removes e from its list, decrements l.len +func (l *List[T]) remove(e *Element[T]) { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- +} + +// move moves e to next to at. +func (l *List[T]) move(e, at *Element[T]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// Remove removes e from l if e is an element of list l. +// It returns the element value e.Value. +// The element must not be nil. +func (l *List[T]) Remove(e *Element[T]) T { + if e.list == l { + // if e.list == l, l must have been initialized when e was inserted + // in l or l == nil (e is a zero Element) and l.remove will crash + l.remove(e) + } + return e.Value +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *List[T]) PushFront(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, &l.root) +} + +// PushBack inserts a new element e with value v at the back of list l and returns e. +func (l *List[T]) PushBack(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, l.root.prev) +} + +// InsertBefore inserts a new element e with value v immediately before mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark.prev) +} + +// InsertAfter inserts a new element e with value v immediately after mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToFront(e *Element[T]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} + +// MoveToBack moves element e to the back of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToBack(e *Element[T]) { + if e.list != l || l.root.prev == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, l.root.prev) +} + +// MoveBefore moves element e to its new position before mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveBefore(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark.prev) +} + +// MoveAfter moves element e to its new position after mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveAfter(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark) +} + +// PushBackList inserts a copy of another list at the back of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushBackList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { + l.insertValue(e.Value, l.root.prev) + } +} + +// PushFrontList inserts a copy of another list at the front of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushFrontList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { + l.insertValue(e.Value, &l.root) + } +} diff --git a/vendor/github.com/buger/jsonparser/.gitignore b/vendor/github.com/buger/jsonparser/.gitignore new file mode 100644 index 000000000..5598d8a56 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/.gitignore @@ -0,0 +1,12 @@ + +*.test + +*.out + +*.mprof + +.idea + +vendor/github.com/buger/goterm/ +prof.cpu +prof.mem diff --git a/vendor/github.com/buger/jsonparser/.travis.yml b/vendor/github.com/buger/jsonparser/.travis.yml new file mode 100644 index 000000000..dbfb7cf98 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/.travis.yml @@ -0,0 +1,11 @@ +language: go +arch: + - amd64 + - ppc64le +go: + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x +script: go test -v ./. diff --git a/vendor/github.com/buger/jsonparser/Dockerfile b/vendor/github.com/buger/jsonparser/Dockerfile new file mode 100644 index 000000000..37fc9fd0b --- /dev/null +++ b/vendor/github.com/buger/jsonparser/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.6 + +RUN go get github.com/Jeffail/gabs +RUN go get github.com/bitly/go-simplejson +RUN go get github.com/pquerna/ffjson +RUN go get github.com/antonholmquist/jason +RUN go get github.com/mreiferson/go-ujson +RUN go get -tags=unsafe -u github.com/ugorji/go/codec +RUN go get github.com/mailru/easyjson + +WORKDIR /go/src/github.com/buger/jsonparser +ADD . /go/src/github.com/buger/jsonparser \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/LICENSE b/vendor/github.com/buger/jsonparser/LICENSE new file mode 100644 index 000000000..ac25aeb7d --- /dev/null +++ b/vendor/github.com/buger/jsonparser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Leonid Bugaev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/buger/jsonparser/Makefile b/vendor/github.com/buger/jsonparser/Makefile new file mode 100644 index 000000000..e843368cf --- /dev/null +++ b/vendor/github.com/buger/jsonparser/Makefile @@ -0,0 +1,36 @@ +SOURCE = parser.go +CONTAINER = jsonparser +SOURCE_PATH = /go/src/github.com/buger/jsonparser +BENCHMARK = JsonParser +BENCHTIME = 5s +TEST = . +DRUN = docker run -v `pwd`:$(SOURCE_PATH) -i -t $(CONTAINER) + +build: + docker build -t $(CONTAINER) . + +race: + $(DRUN) --env GORACE="halt_on_error=1" go test ./. $(ARGS) -v -race -timeout 15s + +bench: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -benchtime $(BENCHTIME) -v + +bench_local: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench . $(ARGS) -benchtime $(BENCHTIME) -v + +profile: + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -memprofile mem.mprof -v + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -cpuprofile cpu.out -v + $(DRUN) go test $(LDFLAGS) -test.benchmem -bench $(BENCHMARK) ./benchmark/ $(ARGS) -c + +test: + $(DRUN) go test $(LDFLAGS) ./ -run $(TEST) -timeout 10s $(ARGS) -v + +fmt: + $(DRUN) go fmt ./... + +vet: + $(DRUN) go vet ./. + +bash: + $(DRUN) /bin/bash \ No newline at end of file diff --git a/vendor/github.com/buger/jsonparser/README.md b/vendor/github.com/buger/jsonparser/README.md new file mode 100644 index 000000000..d7e0ec397 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/README.md @@ -0,0 +1,365 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/buger/jsonparser)](https://goreportcard.com/report/github.com/buger/jsonparser) ![License](https://img.shields.io/dub/l/vibe-d.svg) +# Alternative JSON parser for Go (10x times faster standard library) + +It does not require you to know the structure of the payload (eg. create structs), and allows accessing fields by providing the path to them. It is up to **10 times faster** than standard `encoding/json` package (depending on payload size and usage), **allocates no memory**. See benchmarks below. + +## Rationale +Originally I made this for a project that relies on a lot of 3rd party APIs that can be unpredictable and complex. +I love simplicity and prefer to avoid external dependecies. `encoding/json` requires you to know exactly your data structures, or if you prefer to use `map[string]interface{}` instead, it will be very slow and hard to manage. +I investigated what's on the market and found that most libraries are just wrappers around `encoding/json`, there is few options with own parsers (`ffjson`, `easyjson`), but they still requires you to create data structures. + + +Goal of this project is to push JSON parser to the performance limits and not sacrifice with compliance and developer user experience. + +## Example +For the given JSON our goal is to extract the user's full name, number of github followers and avatar. + +```go +import "github.com/buger/jsonparser" + +... + +data := []byte(`{ + "person": { + "name": { + "first": "Leonid", + "last": "Bugaev", + "fullName": "Leonid Bugaev" + }, + "github": { + "handle": "buger", + "followers": 109 + }, + "avatars": [ + { "url": "https://avatars1.githubusercontent.com/u/14009?v=3&s=460", "type": "thumbnail" } + ] + }, + "company": { + "name": "Acme" + } +}`) + +// You can specify key path by providing arguments to Get function +jsonparser.Get(data, "person", "name", "fullName") + +// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type +jsonparser.GetInt(data, "person", "github", "followers") + +// When you try to get object, it will return you []byte slice pointer to data containing it +// In `company` it will be `{"name": "Acme"}` +jsonparser.Get(data, "company") + +// If the key doesn't exist it will throw an error +var size int64 +if value, err := jsonparser.GetInt(data, "company", "size"); err == nil { + size = value +} + +// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN] +jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { + fmt.Println(jsonparser.Get(value, "url")) +}, "person", "avatars") + +// Or use can access fields by index! +jsonparser.GetString(data, "person", "avatars", "[0]", "url") + +// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN } +jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { + fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType) + return nil +}, "person", "name") + +// The most efficient way to extract multiple keys is `EachKey` + +paths := [][]string{ + []string{"person", "name", "fullName"}, + []string{"person", "avatars", "[0]", "url"}, + []string{"company", "url"}, +} +jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error){ + switch idx { + case 0: // []string{"person", "name", "fullName"} + ... + case 1: // []string{"person", "avatars", "[0]", "url"} + ... + case 2: // []string{"company", "url"}, + ... + } +}, paths...) + +// For more information see docs below +``` + +## Need to speedup your app? + +I'm available for consulting and can help you push your app performance to the limits. Ping me at: leonsbox@gmail.com. + +## Reference + +Library API is really simple. You just need the `Get` method to perform any operation. The rest is just helpers around it. + +You also can view API at [godoc.org](https://godoc.org/github.com/buger/jsonparser) + + +### **`Get`** +```go +func Get(data []byte, keys ...string) (value []byte, dataType jsonparser.ValueType, offset int, err error) +``` +Receives data structure, and key path to extract value from. + +Returns: +* `value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error +* `dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` +* `offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. +* `err` - If the key is not found or any other parsing issue, it should return error. If key not found it also sets `dataType` to `NotExist` + +Accepts multiple keys to specify path to JSON value (in case of quering nested structures). +If no keys are provided it will try to extract the closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. + +Note that keys can be an array indexes: `jsonparser.GetInt("person", "avatars", "[0]", "url")`, pretty cool, yeah? + +### **`GetString`** +```go +func GetString(data []byte, keys ...string) (val string, err error) +``` +Returns strings properly handing escaped and unicode characters. Note that this will cause additional memory allocations. + +### **`GetUnsafeString`** +If you need string in your app, and ready to sacrifice with support of escaped symbols in favor of speed. It returns string mapped to existing byte slice memory, without any allocations: +```go +s, _, := jsonparser.GetUnsafeString(data, "person", "name", "title") +switch s { + case 'CEO': + ... + case 'Engineer' + ... + ... +} +``` +Note that `unsafe` here means that your string will exist until GC will free underlying byte slice, for most of cases it means that you can use this string only in current context, and should not pass it anywhere externally: through channels or any other way. + + +### **`GetBoolean`**, **`GetInt`** and **`GetFloat`** +```go +func GetBoolean(data []byte, keys ...string) (val bool, err error) + +func GetFloat(data []byte, keys ...string) (val float64, err error) + +func GetInt(data []byte, keys ...string) (val int64, err error) +``` +If you know the key type, you can use the helpers above. +If key data type do not match, it will return error. + +### **`ArrayEach`** +```go +func ArrayEach(data []byte, cb func(value []byte, dataType jsonparser.ValueType, offset int, err error), keys ...string) +``` +Needed for iterating arrays, accepts a callback function with the same return arguments as `Get`. + +### **`ObjectEach`** +```go +func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) +``` +Needed for iterating object, accepts a callback function. Example: +```go +var handler func([]byte, []byte, jsonparser.ValueType, int) error +handler = func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error { + //do stuff here +} +jsonparser.ObjectEach(myJson, handler) +``` + + +### **`EachKey`** +```go +func EachKey(data []byte, cb func(idx int, value []byte, dataType jsonparser.ValueType, err error), paths ...[]string) +``` +When you need to read multiple keys, and you do not afraid of low-level API `EachKey` is your friend. It read payload only single time, and calls callback function once path is found. For example when you call multiple times `Get`, it has to process payload multiple times, each time you call it. Depending on payload `EachKey` can be multiple times faster than `Get`. Path can use nested keys as well! + +```go +paths := [][]string{ + []string{"uuid"}, + []string{"tz"}, + []string{"ua"}, + []string{"st"}, +} +var data SmallPayload + +jsonparser.EachKey(smallFixture, func(idx int, value []byte, vt jsonparser.ValueType, err error){ + switch idx { + case 0: + data.Uuid, _ = value + case 1: + v, _ := jsonparser.ParseInt(value) + data.Tz = int(v) + case 2: + data.Ua, _ = value + case 3: + v, _ := jsonparser.ParseInt(value) + data.St = int(v) + } +}, paths...) +``` + +### **`Set`** +```go +func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) +``` +Receives existing data structure, key path to set, and value to set at that key. *This functionality is experimental.* + +Returns: +* `value` - Pointer to original data structure with updated or added key value. +* `err` - If any parsing issue, it should return error. + +Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). + +Note that keys can be an array indexes: `jsonparser.Set(data, []byte("http://github.com"), "person", "avatars", "[0]", "url")` + +### **`Delete`** +```go +func Delete(data []byte, keys ...string) value []byte +``` +Receives existing data structure, and key path to delete. *This functionality is experimental.* + +Returns: +* `value` - Pointer to original data structure with key path deleted if it can be found. If there is no key path, then the whole data structure is deleted. + +Accepts multiple keys to specify path to JSON value (in case of updating or creating nested structures). + +Note that keys can be an array indexes: `jsonparser.Delete(data, "person", "avatars", "[0]", "url")` + + +## What makes it so fast? +* It does not rely on `encoding/json`, `reflection` or `interface{}`, the only real package dependency is `bytes`. +* Operates with JSON payload on byte level, providing you pointers to the original data structure: no memory allocation. +* No automatic type conversions, by default everything is a []byte, but it provides you value type, so you can convert by yourself (there is few helpers included). +* Does not parse full record, only keys you specified + + +## Benchmarks + +There are 3 benchmark types, trying to simulate real-life usage for small, medium and large JSON payloads. +For each metric, the lower value is better. Time/op is in nanoseconds. Values better than standard encoding/json marked as bold text. +Benchmarks run on standard Linode 1024 box. + +Compared libraries: +* https://golang.org/pkg/encoding/json +* https://github.com/Jeffail/gabs +* https://github.com/a8m/djson +* https://github.com/bitly/go-simplejson +* https://github.com/antonholmquist/jason +* https://github.com/mreiferson/go-ujson +* https://github.com/ugorji/go/codec +* https://github.com/pquerna/ffjson +* https://github.com/mailru/easyjson +* https://github.com/buger/jsonparser + +#### TLDR +If you want to skip next sections we have 2 winner: `jsonparser` and `easyjson`. +`jsonparser` is up to 10 times faster than standard `encoding/json` package (depending on payload size and usage), and almost infinitely (literally) better in memory consumption because it operates with data on byte level, and provide direct slice pointers. +`easyjson` wins in CPU in medium tests and frankly i'm impressed with this package: it is remarkable results considering that it is almost drop-in replacement for `encoding/json` (require some code generation). + +It's hard to fully compare `jsonparser` and `easyjson` (or `ffson`), they a true parsers and fully process record, unlike `jsonparser` which parse only keys you specified. + +If you searching for replacement of `encoding/json` while keeping structs, `easyjson` is an amazing choice. If you want to process dynamic JSON, have memory constrains, or more control over your data you should try `jsonparser`. + +`jsonparser` performance heavily depends on usage, and it works best when you do not need to process full record, only some keys. The more calls you need to make, the slower it will be, in contrast `easyjson` (or `ffjson`, `encoding/json`) parser record only 1 time, and then you can make as many calls as you want. + +With great power comes great responsibility! :) + + +#### Small payload + +Each test processes 190 bytes of http log as a JSON record. +It should read multiple fields. +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_small_payload_test.go + +Library | time/op | bytes/op | allocs/op + ------ | ------- | -------- | ------- +encoding/json struct | 7879 | 880 | 18 +encoding/json interface{} | 8946 | 1521 | 38 +Jeffail/gabs | 10053 | 1649 | 46 +bitly/go-simplejson | 10128 | 2241 | 36 +antonholmquist/jason | 27152 | 7237 | 101 +github.com/ugorji/go/codec | 8806 | 2176 | 31 +mreiferson/go-ujson | **7008** | **1409** | 37 +a8m/djson | 3862 | 1249 | 30 +pquerna/ffjson | **3769** | **624** | **15** +mailru/easyjson | **2002** | **192** | **9** +buger/jsonparser | **1367** | **0** | **0** +buger/jsonparser (EachKey API) | **809** | **0** | **0** + +Winners are ffjson, easyjson and jsonparser, where jsonparser is up to 9.8x faster than encoding/json and 4.6x faster than ffjson, and slightly faster than easyjson. +If you look at memory allocation, jsonparser has no rivals, as it makes no data copy and operates with raw []byte structures and pointers to it. + +#### Medium payload + +Each test processes a 2.4kb JSON record (based on Clearbit API). +It should read multiple nested fields and 1 array. + +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_medium_payload_test.go + +| Library | time/op | bytes/op | allocs/op | +| ------- | ------- | -------- | --------- | +| encoding/json struct | 57749 | 1336 | 29 | +| encoding/json interface{} | 79297 | 10627 | 215 | +| Jeffail/gabs | 83807 | 11202 | 235 | +| bitly/go-simplejson | 88187 | 17187 | 220 | +| antonholmquist/jason | 94099 | 19013 | 247 | +| github.com/ugorji/go/codec | 114719 | 6712 | 152 | +| mreiferson/go-ujson | **56972** | 11547 | 270 | +| a8m/djson | 28525 | 10196 | 198 | +| pquerna/ffjson | **20298** | **856** | **20** | +| mailru/easyjson | **10512** | **336** | **12** | +| buger/jsonparser | **15955** | **0** | **0** | +| buger/jsonparser (EachKey API) | **8916** | **0** | **0** | + +The difference between ffjson and jsonparser in CPU usage is smaller, while the memory consumption difference is growing. On the other hand `easyjson` shows remarkable performance for medium payload. + +`gabs`, `go-simplejson` and `jason` are based on encoding/json and map[string]interface{} and actually only helpers for unstructured JSON, their performance correlate with `encoding/json interface{}`, and they will skip next round. +`go-ujson` while have its own parser, shows same performance as `encoding/json`, also skips next round. Same situation with `ugorji/go/codec`, but it showed unexpectedly bad performance for complex payloads. + + +#### Large payload + +Each test processes a 24kb JSON record (based on Discourse API) +It should read 2 arrays, and for each item in array get a few fields. +Basically it means processing a full JSON file. + +https://github.com/buger/jsonparser/blob/master/benchmark/benchmark_large_payload_test.go + +| Library | time/op | bytes/op | allocs/op | +| --- | --- | --- | --- | +| encoding/json struct | 748336 | 8272 | 307 | +| encoding/json interface{} | 1224271 | 215425 | 3395 | +| a8m/djson | 510082 | 213682 | 2845 | +| pquerna/ffjson | **312271** | **7792** | **298** | +| mailru/easyjson | **154186** | **6992** | **288** | +| buger/jsonparser | **85308** | **0** | **0** | + +`jsonparser` now is a winner, but do not forget that it is way more lightweight parser than `ffson` or `easyjson`, and they have to parser all the data, while `jsonparser` parse only what you need. All `ffjson`, `easysjon` and `jsonparser` have their own parsing code, and does not depend on `encoding/json` or `interface{}`, thats one of the reasons why they are so fast. `easyjson` also use a bit of `unsafe` package to reduce memory consuption (in theory it can lead to some unexpected GC issue, but i did not tested enough) + +Also last benchmark did not included `EachKey` test, because in this particular case we need to read lot of Array values, and using `ArrayEach` is more efficient. + +## Questions and support + +All bug-reports and suggestions should go though Github Issues. + +## Contributing + +1. Fork it +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Added some feature') +4. Push to the branch (git push origin my-new-feature) +5. Create new Pull Request + +## Development + +All my development happens using Docker, and repo include some Make tasks to simplify development. + +* `make build` - builds docker image, usually can be called only once +* `make test` - run tests +* `make fmt` - run go fmt +* `make bench` - run benchmarks (if you need to run only single benchmark modify `BENCHMARK` variable in make file) +* `make profile` - runs benchmark and generate 3 files- `cpu.out`, `mem.mprof` and `benchmark.test` binary, which can be used for `go tool pprof` +* `make bash` - enter container (i use it for running `go tool pprof` above) diff --git a/vendor/github.com/buger/jsonparser/bytes.go b/vendor/github.com/buger/jsonparser/bytes.go new file mode 100644 index 000000000..0bb0ff395 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes.go @@ -0,0 +1,47 @@ +package jsonparser + +import ( + bio "bytes" +) + +// minInt64 '-9223372036854775808' is the smallest representable number in int64 +const minInt64 = `9223372036854775808` + +// About 2x faster then strconv.ParseInt because it only supports base 10, which is enough for JSON +func parseInt(bytes []byte) (v int64, ok bool, overflow bool) { + if len(bytes) == 0 { + return 0, false, false + } + + var neg bool = false + if bytes[0] == '-' { + neg = true + bytes = bytes[1:] + } + + var b int64 = 0 + for _, c := range bytes { + if c >= '0' && c <= '9' { + b = (10 * v) + int64(c-'0') + } else { + return 0, false, false + } + if overflow = (b < v); overflow { + break + } + v = b + } + + if overflow { + if neg && bio.Equal(bytes, []byte(minInt64)) { + return b, true, false + } + return 0, false, true + } + + if neg { + return -v, true, false + } else { + return v, true, false + } +} diff --git a/vendor/github.com/buger/jsonparser/bytes_safe.go b/vendor/github.com/buger/jsonparser/bytes_safe.go new file mode 100644 index 000000000..ff16a4a19 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes_safe.go @@ -0,0 +1,25 @@ +// +build appengine appenginevm + +package jsonparser + +import ( + "strconv" +) + +// See fastbytes_unsafe.go for explanation on why *[]byte is used (signatures must be consistent with those in that file) + +func equalStr(b *[]byte, s string) bool { + return string(*b) == s +} + +func parseFloat(b *[]byte) (float64, error) { + return strconv.ParseFloat(string(*b), 64) +} + +func bytesToString(b *[]byte) string { + return string(*b) +} + +func StringToBytes(s string) []byte { + return []byte(s) +} diff --git a/vendor/github.com/buger/jsonparser/bytes_unsafe.go b/vendor/github.com/buger/jsonparser/bytes_unsafe.go new file mode 100644 index 000000000..589fea87e --- /dev/null +++ b/vendor/github.com/buger/jsonparser/bytes_unsafe.go @@ -0,0 +1,44 @@ +// +build !appengine,!appenginevm + +package jsonparser + +import ( + "reflect" + "strconv" + "unsafe" + "runtime" +) + +// +// The reason for using *[]byte rather than []byte in parameters is an optimization. As of Go 1.6, +// the compiler cannot perfectly inline the function when using a non-pointer slice. That is, +// the non-pointer []byte parameter version is slower than if its function body is manually +// inlined, whereas the pointer []byte version is equally fast to the manually inlined +// version. Instruction count in assembly taken from "go tool compile" confirms this difference. +// +// TODO: Remove hack after Go 1.7 release +// +func equalStr(b *[]byte, s string) bool { + return *(*string)(unsafe.Pointer(b)) == s +} + +func parseFloat(b *[]byte) (float64, error) { + return strconv.ParseFloat(*(*string)(unsafe.Pointer(b)), 64) +} + +// A hack until issue golang/go#2632 is fixed. +// See: https://github.com/golang/go/issues/2632 +func bytesToString(b *[]byte) string { + return *(*string)(unsafe.Pointer(b)) +} + +func StringToBytes(s string) []byte { + b := make([]byte, 0, 0) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data = sh.Data + bh.Cap = sh.Len + bh.Len = sh.Len + runtime.KeepAlive(s) + return b +} diff --git a/vendor/github.com/buger/jsonparser/escape.go b/vendor/github.com/buger/jsonparser/escape.go new file mode 100644 index 000000000..49669b942 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/escape.go @@ -0,0 +1,173 @@ +package jsonparser + +import ( + "bytes" + "unicode/utf8" +) + +// JSON Unicode stuff: see https://tools.ietf.org/html/rfc7159#section-7 + +const supplementalPlanesOffset = 0x10000 +const highSurrogateOffset = 0xD800 +const lowSurrogateOffset = 0xDC00 + +const basicMultilingualPlaneReservedOffset = 0xDFFF +const basicMultilingualPlaneOffset = 0xFFFF + +func combineUTF16Surrogates(high, low rune) rune { + return supplementalPlanesOffset + (high-highSurrogateOffset)<<10 + (low - lowSurrogateOffset) +} + +const badHex = -1 + +func h2I(c byte) int { + switch { + case c >= '0' && c <= '9': + return int(c - '0') + case c >= 'A' && c <= 'F': + return int(c - 'A' + 10) + case c >= 'a' && c <= 'f': + return int(c - 'a' + 10) + } + return badHex +} + +// decodeSingleUnicodeEscape decodes a single \uXXXX escape sequence. The prefix \u is assumed to be present and +// is not checked. +// In JSON, these escapes can either come alone or as part of "UTF16 surrogate pairs" that must be handled together. +// This function only handles one; decodeUnicodeEscape handles this more complex case. +func decodeSingleUnicodeEscape(in []byte) (rune, bool) { + // We need at least 6 characters total + if len(in) < 6 { + return utf8.RuneError, false + } + + // Convert hex to decimal + h1, h2, h3, h4 := h2I(in[2]), h2I(in[3]), h2I(in[4]), h2I(in[5]) + if h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex { + return utf8.RuneError, false + } + + // Compose the hex digits + return rune(h1<<12 + h2<<8 + h3<<4 + h4), true +} + +// isUTF16EncodedRune checks if a rune is in the range for non-BMP characters, +// which is used to describe UTF16 chars. +// Source: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane +func isUTF16EncodedRune(r rune) bool { + return highSurrogateOffset <= r && r <= basicMultilingualPlaneReservedOffset +} + +func decodeUnicodeEscape(in []byte) (rune, int) { + if r, ok := decodeSingleUnicodeEscape(in); !ok { + // Invalid Unicode escape + return utf8.RuneError, -1 + } else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) { + // Valid Unicode escape in Basic Multilingual Plane + return r, 6 + } else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain + // UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate" + return utf8.RuneError, -1 + } else if r2 < lowSurrogateOffset { + // Invalid UTF16 "low surrogate" + return utf8.RuneError, -1 + } else { + // Valid UTF16 surrogate pair + return combineUTF16Surrogates(r, r2), 12 + } +} + +// backslashCharEscapeTable: when '\X' is found for some byte X, it is to be replaced with backslashCharEscapeTable[X] +var backslashCharEscapeTable = [...]byte{ + '"': '"', + '\\': '\\', + '/': '/', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t', +} + +// unescapeToUTF8 unescapes the single escape sequence starting at 'in' into 'out' and returns +// how many characters were consumed from 'in' and emitted into 'out'. +// If a valid escape sequence does not appear as a prefix of 'in', (-1, -1) to signal the error. +func unescapeToUTF8(in, out []byte) (inLen int, outLen int) { + if len(in) < 2 || in[0] != '\\' { + // Invalid escape due to insufficient characters for any escape or no initial backslash + return -1, -1 + } + + // https://tools.ietf.org/html/rfc7159#section-7 + switch e := in[1]; e { + case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': + // Valid basic 2-character escapes (use lookup table) + out[0] = backslashCharEscapeTable[e] + return 2, 1 + case 'u': + // Unicode escape + if r, inLen := decodeUnicodeEscape(in); inLen == -1 { + // Invalid Unicode escape + return -1, -1 + } else { + // Valid Unicode escape; re-encode as UTF8 + outLen := utf8.EncodeRune(out, r) + return inLen, outLen + } + } + + return -1, -1 +} + +// unescape unescapes the string contained in 'in' and returns it as a slice. +// If 'in' contains no escaped characters: +// Returns 'in'. +// Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)): +// 'out' is used to build the unescaped string and is returned with no extra allocation +// Else: +// A new slice is allocated and returned. +func Unescape(in, out []byte) ([]byte, error) { + firstBackslash := bytes.IndexByte(in, '\\') + if firstBackslash == -1 { + return in, nil + } + + // Get a buffer of sufficient size (allocate if needed) + if cap(out) < len(in) { + out = make([]byte, len(in)) + } else { + out = out[0:len(in)] + } + + // Copy the first sequence of unescaped bytes to the output and obtain a buffer pointer (subslice) + copy(out, in[:firstBackslash]) + in = in[firstBackslash:] + buf := out[firstBackslash:] + + for len(in) > 0 { + // Unescape the next escaped character + inLen, bufLen := unescapeToUTF8(in, buf) + if inLen == -1 { + return nil, MalformedStringEscapeError + } + + in = in[inLen:] + buf = buf[bufLen:] + + // Copy everything up until the next backslash + nextBackslash := bytes.IndexByte(in, '\\') + if nextBackslash == -1 { + copy(buf, in) + buf = buf[len(in):] + break + } else { + copy(buf, in[:nextBackslash]) + buf = buf[nextBackslash:] + in = in[nextBackslash:] + } + } + + // Trim the out buffer to the amount that was actually emitted + return out[:len(out)-len(buf)], nil +} diff --git a/vendor/github.com/buger/jsonparser/fuzz.go b/vendor/github.com/buger/jsonparser/fuzz.go new file mode 100644 index 000000000..854bd11b2 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/fuzz.go @@ -0,0 +1,117 @@ +package jsonparser + +func FuzzParseString(data []byte) int { + r, err := ParseString(data) + if err != nil || r == "" { + return 0 + } + return 1 +} + +func FuzzEachKey(data []byte) int { + paths := [][]string{ + {"name"}, + {"order"}, + {"nested", "a"}, + {"nested", "b"}, + {"nested2", "a"}, + {"nested", "nested3", "b"}, + {"arr", "[1]", "b"}, + {"arrInt", "[3]"}, + {"arrInt", "[5]"}, + {"nested"}, + {"arr", "["}, + {"a\n", "b\n"}, + } + EachKey(data, func(idx int, value []byte, vt ValueType, err error) {}, paths...) + return 1 +} + +func FuzzDelete(data []byte) int { + Delete(data, "test") + return 1 +} + +func FuzzSet(data []byte) int { + _, err := Set(data, []byte(`"new value"`), "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzObjectEach(data []byte) int { + _ = ObjectEach(data, func(key, value []byte, valueType ValueType, off int) error { + return nil + }) + return 1 +} + +func FuzzParseFloat(data []byte) int { + _, err := ParseFloat(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzParseInt(data []byte) int { + _, err := ParseInt(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzParseBool(data []byte) int { + _, err := ParseBoolean(data) + if err != nil { + return 0 + } + return 1 +} + +func FuzzTokenStart(data []byte) int { + _ = tokenStart(data) + return 1 +} + +func FuzzGetString(data []byte) int { + _, err := GetString(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetFloat(data []byte) int { + _, err := GetFloat(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetInt(data []byte) int { + _, err := GetInt(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetBoolean(data []byte) int { + _, err := GetBoolean(data, "test") + if err != nil { + return 0 + } + return 1 +} + +func FuzzGetUnsafeString(data []byte) int { + _, err := GetUnsafeString(data, "test") + if err != nil { + return 0 + } + return 1 +} diff --git a/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh b/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh new file mode 100644 index 000000000..c573b0e2d --- /dev/null +++ b/vendor/github.com/buger/jsonparser/oss-fuzz-build.sh @@ -0,0 +1,47 @@ +#!/bin/bash -eu + +git clone https://github.com/dvyukov/go-fuzz-corpus +zip corpus.zip go-fuzz-corpus/json/corpus/* + +cp corpus.zip $OUT/fuzzparsestring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseString fuzzparsestring + +cp corpus.zip $OUT/fuzzeachkey_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzEachKey fuzzeachkey + +cp corpus.zip $OUT/fuzzdelete_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzDelete fuzzdelete + +cp corpus.zip $OUT/fuzzset_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzSet fuzzset + +cp corpus.zip $OUT/fuzzobjecteach_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzObjectEach fuzzobjecteach + +cp corpus.zip $OUT/fuzzparsefloat_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseFloat fuzzparsefloat + +cp corpus.zip $OUT/fuzzparseint_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseInt fuzzparseint + +cp corpus.zip $OUT/fuzzparsebool_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzParseBool fuzzparsebool + +cp corpus.zip $OUT/fuzztokenstart_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzTokenStart fuzztokenstart + +cp corpus.zip $OUT/fuzzgetstring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetString fuzzgetstring + +cp corpus.zip $OUT/fuzzgetfloat_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetFloat fuzzgetfloat + +cp corpus.zip $OUT/fuzzgetint_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetInt fuzzgetint + +cp corpus.zip $OUT/fuzzgetboolean_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetBoolean fuzzgetboolean + +cp corpus.zip $OUT/fuzzgetunsafestring_seed_corpus.zip +compile_go_fuzzer github.com/buger/jsonparser FuzzGetUnsafeString fuzzgetunsafestring + diff --git a/vendor/github.com/buger/jsonparser/parser.go b/vendor/github.com/buger/jsonparser/parser.go new file mode 100644 index 000000000..14b80bc48 --- /dev/null +++ b/vendor/github.com/buger/jsonparser/parser.go @@ -0,0 +1,1283 @@ +package jsonparser + +import ( + "bytes" + "errors" + "fmt" + "strconv" +) + +// Errors +var ( + KeyPathNotFoundError = errors.New("Key path not found") + UnknownValueTypeError = errors.New("Unknown value type") + MalformedJsonError = errors.New("Malformed JSON error") + MalformedStringError = errors.New("Value is string, but can't find closing '\"' symbol") + MalformedArrayError = errors.New("Value is array, but can't find closing ']' symbol") + MalformedObjectError = errors.New("Value looks like object, but can't find closing '}' symbol") + MalformedValueError = errors.New("Value looks like Number/Boolean/None, but can't find its end: ',' or '}' symbol") + OverflowIntegerError = errors.New("Value is number, but overflowed while parsing") + MalformedStringEscapeError = errors.New("Encountered an invalid escape sequence in a string") +) + +// How much stack space to allocate for unescaping JSON strings; if a string longer +// than this needs to be escaped, it will result in a heap allocation +const unescapeStackBufSize = 64 + +func tokenEnd(data []byte) int { + for i, c := range data { + switch c { + case ' ', '\n', '\r', '\t', ',', '}', ']': + return i + } + } + + return len(data) +} + +func findTokenStart(data []byte, token byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case token: + return i + case '[', '{': + return 0 + } + } + + return 0 +} + +func findKeyStart(data []byte, key string) (int, error) { + i := 0 + ln := len(data) + if ln > 0 && (data[0] == '{' || data[0] == '[') { + i = 1 + } + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + + if ku, err := Unescape(StringToBytes(key), stackbuf[:]); err == nil { + key = bytesToString(&ku) + } + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + break + } + i += strEnd + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + break + } + + i += valueOffset + + // if string is a key, and key level match + k := data[keyBegin:keyEnd] + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + if keyEscaped { + if ku, err := Unescape(k, stackbuf[:]); err != nil { + break + } else { + k = ku + } + } + + if data[i] == ':' && len(key) == len(k) && bytesToString(&k) == key { + return keyBegin - 1, nil + } + + case '[': + end := blockEnd(data[i:], data[i], ']') + if end != -1 { + i = i + end + } + case '{': + end := blockEnd(data[i:], data[i], '}') + if end != -1 { + i = i + end + } + } + i++ + } + + return -1, KeyPathNotFoundError +} + +func tokenStart(data []byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case '\n', '\r', '\t', ',', '{', '[': + return i + } + } + + return 0 +} + +// Find position of next character which is not whitespace +func nextToken(data []byte) int { + for i, c := range data { + switch c { + case ' ', '\n', '\r', '\t': + continue + default: + return i + } + } + + return -1 +} + +// Find position of last character which is not whitespace +func lastToken(data []byte) int { + for i := len(data) - 1; i >= 0; i-- { + switch data[i] { + case ' ', '\n', '\r', '\t': + continue + default: + return i + } + } + + return -1 +} + +// Tries to find the end of string +// Support if string contains escaped quote symbols. +func stringEnd(data []byte) (int, bool) { + escaped := false + for i, c := range data { + if c == '"' { + if !escaped { + return i + 1, false + } else { + j := i - 1 + for { + if j < 0 || data[j] != '\\' { + return i + 1, true // even number of backslashes + } + j-- + if j < 0 || data[j] != '\\' { + break // odd number of backslashes + } + j-- + + } + } + } else if c == '\\' { + escaped = true + } + } + + return -1, escaped +} + +// Find end of the data structure, array or object. +// For array openSym and closeSym will be '[' and ']', for object '{' and '}' +func blockEnd(data []byte, openSym byte, closeSym byte) int { + level := 0 + i := 0 + ln := len(data) + + for i < ln { + switch data[i] { + case '"': // If inside string, skip it + se, _ := stringEnd(data[i+1:]) + if se == -1 { + return -1 + } + i += se + case openSym: // If open symbol, increase level + level++ + case closeSym: // If close symbol, increase level + level-- + + // If we have returned to the original level, we're done + if level == 0 { + return i + 1 + } + } + i++ + } + + return -1 +} + +func searchKeys(data []byte, keys ...string) int { + keyLevel := 0 + level := 0 + i := 0 + ln := len(data) + lk := len(keys) + lastMatched := true + + if lk == 0 { + return 0 + } + + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + return -1 + } + i += strEnd + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + return -1 + } + + i += valueOffset + + // if string is a key + if data[i] == ':' { + if level < 1 { + return -1 + } + + key := data[keyBegin:keyEnd] + + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + var keyUnesc []byte + if !keyEscaped { + keyUnesc = key + } else if ku, err := Unescape(key, stackbuf[:]); err != nil { + return -1 + } else { + keyUnesc = ku + } + + if level <= len(keys) { + if equalStr(&keyUnesc, keys[level-1]) { + lastMatched = true + + // if key level match + if keyLevel == level-1 { + keyLevel++ + // If we found all keys in path + if keyLevel == lk { + return i + 1 + } + } + } else { + lastMatched = false + } + } else { + return -1 + } + } else { + i-- + } + case '{': + + // in case parent key is matched then only we will increase the level otherwise can directly + // can move to the end of this block + if !lastMatched { + end := blockEnd(data[i:], '{', '}') + if end == -1 { + return -1 + } + i += end - 1 + } else { + level++ + } + case '}': + level-- + if level == keyLevel { + keyLevel-- + } + case '[': + // If we want to get array element by index + if keyLevel == level && keys[level][0] == '[' { + var keyLen = len(keys[level]) + if keyLen < 3 || keys[level][0] != '[' || keys[level][keyLen-1] != ']' { + return -1 + } + aIdx, err := strconv.Atoi(keys[level][1 : keyLen-1]) + if err != nil { + return -1 + } + var curIdx int + var valueFound []byte + var valueOffset int + var curI = i + ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { + if curIdx == aIdx { + valueFound = value + valueOffset = offset + if dataType == String { + valueOffset = valueOffset - 2 + valueFound = data[curI+valueOffset : curI+valueOffset+len(value)+2] + } + } + curIdx += 1 + }) + + if valueFound == nil { + return -1 + } else { + subIndex := searchKeys(valueFound, keys[level+1:]...) + if subIndex < 0 { + return -1 + } + return i + valueOffset + subIndex + } + } else { + // Do not search for keys inside arrays + if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { + return -1 + } else { + i += arraySkip - 1 + } + } + case ':': // If encountered, JSON data is malformed + return -1 + } + + i++ + } + + return -1 +} + +func sameTree(p1, p2 []string) bool { + minLen := len(p1) + if len(p2) < minLen { + minLen = len(p2) + } + + for pi_1, p_1 := range p1[:minLen] { + if p2[pi_1] != p_1 { + return false + } + } + + return true +} + +func EachKey(data []byte, cb func(int, []byte, ValueType, error), paths ...[]string) int { + var x struct{} + pathFlags := make([]bool, len(paths)) + var level, pathsMatched, i int + ln := len(data) + + var maxPath int + for _, p := range paths { + if len(p) > maxPath { + maxPath = len(p) + } + } + + pathsBuf := make([]string, maxPath) + + for i < ln { + switch data[i] { + case '"': + i++ + keyBegin := i + + strEnd, keyEscaped := stringEnd(data[i:]) + if strEnd == -1 { + return -1 + } + i += strEnd + + keyEnd := i - 1 + + valueOffset := nextToken(data[i:]) + if valueOffset == -1 { + return -1 + } + + i += valueOffset + + // if string is a key, and key level match + if data[i] == ':' { + match := -1 + key := data[keyBegin:keyEnd] + + // for unescape: if there are no escape sequences, this is cheap; if there are, it is a + // bit more expensive, but causes no allocations unless len(key) > unescapeStackBufSize + var keyUnesc []byte + if !keyEscaped { + keyUnesc = key + } else { + var stackbuf [unescapeStackBufSize]byte + if ku, err := Unescape(key, stackbuf[:]); err != nil { + return -1 + } else { + keyUnesc = ku + } + } + + if maxPath >= level { + if level < 1 { + cb(-1, nil, Unknown, MalformedJsonError) + return -1 + } + + pathsBuf[level-1] = bytesToString(&keyUnesc) + for pi, p := range paths { + if len(p) != level || pathFlags[pi] || !equalStr(&keyUnesc, p[level-1]) || !sameTree(p, pathsBuf[:level]) { + continue + } + + match = pi + + pathsMatched++ + pathFlags[pi] = true + + v, dt, _, e := Get(data[i+1:]) + cb(pi, v, dt, e) + + if pathsMatched == len(paths) { + break + } + } + if pathsMatched == len(paths) { + return i + } + } + + if match == -1 { + tokenOffset := nextToken(data[i+1:]) + i += tokenOffset + + if data[i] == '{' { + blockSkip := blockEnd(data[i:], '{', '}') + i += blockSkip + 1 + } + } + + if i < ln { + switch data[i] { + case '{', '}', '[', '"': + i-- + } + } + } else { + i-- + } + case '{': + level++ + case '}': + level-- + case '[': + var ok bool + arrIdxFlags := make(map[int]struct{}) + pIdxFlags := make([]bool, len(paths)) + + if level < 0 { + cb(-1, nil, Unknown, MalformedJsonError) + return -1 + } + + for pi, p := range paths { + if len(p) < level+1 || pathFlags[pi] || p[level][0] != '[' || !sameTree(p, pathsBuf[:level]) { + continue + } + if len(p[level]) >= 2 { + aIdx, _ := strconv.Atoi(p[level][1 : len(p[level])-1]) + arrIdxFlags[aIdx] = x + pIdxFlags[pi] = true + } + } + + if len(arrIdxFlags) > 0 { + level++ + + var curIdx int + arrOff, _ := ArrayEach(data[i:], func(value []byte, dataType ValueType, offset int, err error) { + if _, ok = arrIdxFlags[curIdx]; ok { + for pi, p := range paths { + if pIdxFlags[pi] { + aIdx, _ := strconv.Atoi(p[level-1][1 : len(p[level-1])-1]) + + if curIdx == aIdx { + of := searchKeys(value, p[level:]...) + + pathsMatched++ + pathFlags[pi] = true + + if of != -1 { + v, dt, _, e := Get(value[of:]) + cb(pi, v, dt, e) + } + } + } + } + } + + curIdx += 1 + }) + + if pathsMatched == len(paths) { + return i + } + + i += arrOff - 1 + } else { + // Do not search for keys inside arrays + if arraySkip := blockEnd(data[i:], '[', ']'); arraySkip == -1 { + return -1 + } else { + i += arraySkip - 1 + } + } + case ']': + level-- + } + + i++ + } + + return -1 +} + +// Data types available in valid JSON data. +type ValueType int + +const ( + NotExist = ValueType(iota) + String + Number + Object + Array + Boolean + Null + Unknown +) + +func (vt ValueType) String() string { + switch vt { + case NotExist: + return "non-existent" + case String: + return "string" + case Number: + return "number" + case Object: + return "object" + case Array: + return "array" + case Boolean: + return "boolean" + case Null: + return "null" + default: + return "unknown" + } +} + +var ( + trueLiteral = []byte("true") + falseLiteral = []byte("false") + nullLiteral = []byte("null") +) + +func createInsertComponent(keys []string, setValue []byte, comma, object bool) []byte { + isIndex := string(keys[0][0]) == "[" + offset := 0 + lk := calcAllocateSpace(keys, setValue, comma, object) + buffer := make([]byte, lk, lk) + if comma { + offset += WriteToBuffer(buffer[offset:], ",") + } + if isIndex && !comma { + offset += WriteToBuffer(buffer[offset:], "[") + } else { + if object { + offset += WriteToBuffer(buffer[offset:], "{") + } + if !isIndex { + offset += WriteToBuffer(buffer[offset:], "\"") + offset += WriteToBuffer(buffer[offset:], keys[0]) + offset += WriteToBuffer(buffer[offset:], "\":") + } + } + + for i := 1; i < len(keys); i++ { + if string(keys[i][0]) == "[" { + offset += WriteToBuffer(buffer[offset:], "[") + } else { + offset += WriteToBuffer(buffer[offset:], "{\"") + offset += WriteToBuffer(buffer[offset:], keys[i]) + offset += WriteToBuffer(buffer[offset:], "\":") + } + } + offset += WriteToBuffer(buffer[offset:], string(setValue)) + for i := len(keys) - 1; i > 0; i-- { + if string(keys[i][0]) == "[" { + offset += WriteToBuffer(buffer[offset:], "]") + } else { + offset += WriteToBuffer(buffer[offset:], "}") + } + } + if isIndex && !comma { + offset += WriteToBuffer(buffer[offset:], "]") + } + if object && !isIndex { + offset += WriteToBuffer(buffer[offset:], "}") + } + return buffer +} + +func calcAllocateSpace(keys []string, setValue []byte, comma, object bool) int { + isIndex := string(keys[0][0]) == "[" + lk := 0 + if comma { + // , + lk += 1 + } + if isIndex && !comma { + // [] + lk += 2 + } else { + if object { + // { + lk += 1 + } + if !isIndex { + // "keys[0]" + lk += len(keys[0]) + 3 + } + } + + + lk += len(setValue) + for i := 1; i < len(keys); i++ { + if string(keys[i][0]) == "[" { + // [] + lk += 2 + } else { + // {"keys[i]":setValue} + lk += len(keys[i]) + 5 + } + } + + if object && !isIndex { + // } + lk += 1 + } + + return lk +} + +func WriteToBuffer(buffer []byte, str string) int { + copy(buffer, str) + return len(str) +} + +/* + +Del - Receives existing data structure, path to delete. + +Returns: +`data` - return modified data + +*/ +func Delete(data []byte, keys ...string) []byte { + lk := len(keys) + if lk == 0 { + return data[:0] + } + + array := false + if len(keys[lk-1]) > 0 && string(keys[lk-1][0]) == "[" { + array = true + } + + var startOffset, keyOffset int + endOffset := len(data) + var err error + if !array { + if len(keys) > 1 { + _, _, startOffset, endOffset, err = internalGet(data, keys[:lk-1]...) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + } + + keyOffset, err = findKeyStart(data[startOffset:endOffset], keys[lk-1]) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + keyOffset += startOffset + _, _, _, subEndOffset, _ := internalGet(data[startOffset:endOffset], keys[lk-1]) + endOffset = startOffset + subEndOffset + tokEnd := tokenEnd(data[endOffset:]) + tokStart := findTokenStart(data[:keyOffset], ","[0]) + + if data[endOffset+tokEnd] == ","[0] { + endOffset += tokEnd + 1 + } else if data[endOffset+tokEnd] == " "[0] && len(data) > endOffset+tokEnd+1 && data[endOffset+tokEnd+1] == ","[0] { + endOffset += tokEnd + 2 + } else if data[endOffset+tokEnd] == "}"[0] && data[tokStart] == ","[0] { + keyOffset = tokStart + } + } else { + _, _, keyOffset, endOffset, err = internalGet(data, keys...) + if err == KeyPathNotFoundError { + // problem parsing the data + return data + } + + tokEnd := tokenEnd(data[endOffset:]) + tokStart := findTokenStart(data[:keyOffset], ","[0]) + + if data[endOffset+tokEnd] == ","[0] { + endOffset += tokEnd + 1 + } else if data[endOffset+tokEnd] == "]"[0] && data[tokStart] == ","[0] { + keyOffset = tokStart + } + } + + // We need to remove remaining trailing comma if we delete las element in the object + prevTok := lastToken(data[:keyOffset]) + remainedValue := data[endOffset:] + + var newOffset int + if nextToken(remainedValue) > -1 && remainedValue[nextToken(remainedValue)] == '}' && data[prevTok] == ',' { + newOffset = prevTok + } else { + newOffset = prevTok + 1 + } + + // We have to make a copy here if we don't want to mangle the original data, because byte slices are + // accessed by reference and not by value + dataCopy := make([]byte, len(data)) + copy(dataCopy, data) + data = append(dataCopy[:newOffset], dataCopy[endOffset:]...) + + return data +} + +/* + +Set - Receives existing data structure, path to set, and data to set at that key. + +Returns: +`value` - modified byte array +`err` - On any parsing error + +*/ +func Set(data []byte, setValue []byte, keys ...string) (value []byte, err error) { + // ensure keys are set + if len(keys) == 0 { + return nil, KeyPathNotFoundError + } + + _, _, startOffset, endOffset, err := internalGet(data, keys...) + if err != nil { + if err != KeyPathNotFoundError { + // problem parsing the data + return nil, err + } + // full path doesnt exist + // does any subpath exist? + var depth int + for i := range keys { + _, _, start, end, sErr := internalGet(data, keys[:i+1]...) + if sErr != nil { + break + } else { + endOffset = end + startOffset = start + depth++ + } + } + comma := true + object := false + if endOffset == -1 { + firstToken := nextToken(data) + // We can't set a top-level key if data isn't an object + if firstToken < 0 || data[firstToken] != '{' { + return nil, KeyPathNotFoundError + } + // Don't need a comma if the input is an empty object + secondToken := firstToken + 1 + nextToken(data[firstToken+1:]) + if data[secondToken] == '}' { + comma = false + } + // Set the top level key at the end (accounting for any trailing whitespace) + // This assumes last token is valid like '}', could check and return error + endOffset = lastToken(data) + } + depthOffset := endOffset + if depth != 0 { + // if subpath is a non-empty object, add to it + // or if subpath is a non-empty array, add to it + if (data[startOffset] == '{' && data[startOffset+1+nextToken(data[startOffset+1:])] != '}') || + (data[startOffset] == '[' && data[startOffset+1+nextToken(data[startOffset+1:])] == '{') && keys[depth:][0][0] == 91 { + depthOffset-- + startOffset = depthOffset + // otherwise, over-write it with a new object + } else { + comma = false + object = true + } + } else { + startOffset = depthOffset + } + value = append(data[:startOffset], append(createInsertComponent(keys[depth:], setValue, comma, object), data[depthOffset:]...)...) + } else { + // path currently exists + startComponent := data[:startOffset] + endComponent := data[endOffset:] + + value = make([]byte, len(startComponent)+len(endComponent)+len(setValue)) + newEndOffset := startOffset + len(setValue) + copy(value[0:startOffset], startComponent) + copy(value[startOffset:newEndOffset], setValue) + copy(value[newEndOffset:], endComponent) + } + return value, nil +} + +func getType(data []byte, offset int) ([]byte, ValueType, int, error) { + var dataType ValueType + endOffset := offset + + // if string value + if data[offset] == '"' { + dataType = String + if idx, _ := stringEnd(data[offset+1:]); idx != -1 { + endOffset += idx + 1 + } else { + return nil, dataType, offset, MalformedStringError + } + } else if data[offset] == '[' { // if array value + dataType = Array + // break label, for stopping nested loops + endOffset = blockEnd(data[offset:], '[', ']') + + if endOffset == -1 { + return nil, dataType, offset, MalformedArrayError + } + + endOffset += offset + } else if data[offset] == '{' { // if object value + dataType = Object + // break label, for stopping nested loops + endOffset = blockEnd(data[offset:], '{', '}') + + if endOffset == -1 { + return nil, dataType, offset, MalformedObjectError + } + + endOffset += offset + } else { + // Number, Boolean or None + end := tokenEnd(data[endOffset:]) + + if end == -1 { + return nil, dataType, offset, MalformedValueError + } + + value := data[offset : endOffset+end] + + switch data[offset] { + case 't', 'f': // true or false + if bytes.Equal(value, trueLiteral) || bytes.Equal(value, falseLiteral) { + dataType = Boolean + } else { + return nil, Unknown, offset, UnknownValueTypeError + } + case 'u', 'n': // undefined or null + if bytes.Equal(value, nullLiteral) { + dataType = Null + } else { + return nil, Unknown, offset, UnknownValueTypeError + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': + dataType = Number + default: + return nil, Unknown, offset, UnknownValueTypeError + } + + endOffset += end + } + return data[offset:endOffset], dataType, endOffset, nil +} + +/* +Get - Receives data structure, and key path to extract value from. + +Returns: +`value` - Pointer to original data structure containing key value, or just empty slice if nothing found or error +`dataType` - Can be: `NotExist`, `String`, `Number`, `Object`, `Array`, `Boolean` or `Null` +`offset` - Offset from provided data structure where key value ends. Used mostly internally, for example for `ArrayEach` helper. +`err` - If key not found or any other parsing issue it should return error. If key not found it also sets `dataType` to `NotExist` + +Accept multiple keys to specify path to JSON value (in case of quering nested structures). +If no keys provided it will try to extract closest JSON value (simple ones or object/array), useful for reading streams or arrays, see `ArrayEach` implementation. +*/ +func Get(data []byte, keys ...string) (value []byte, dataType ValueType, offset int, err error) { + a, b, _, d, e := internalGet(data, keys...) + return a, b, d, e +} + +func internalGet(data []byte, keys ...string) (value []byte, dataType ValueType, offset, endOffset int, err error) { + if len(keys) > 0 { + if offset = searchKeys(data, keys...); offset == -1 { + return nil, NotExist, -1, -1, KeyPathNotFoundError + } + } + + // Go to closest value + nO := nextToken(data[offset:]) + if nO == -1 { + return nil, NotExist, offset, -1, MalformedJsonError + } + + offset += nO + value, dataType, endOffset, err = getType(data, offset) + if err != nil { + return value, dataType, offset, endOffset, err + } + + // Strip quotes from string values + if dataType == String { + value = value[1 : len(value)-1] + } + + return value[:len(value):len(value)], dataType, offset, endOffset, nil +} + +// ArrayEach is used when iterating arrays, accepts a callback function with the same return arguments as `Get`. +func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error) { + if len(data) == 0 { + return -1, MalformedObjectError + } + + nT := nextToken(data) + if nT == -1 { + return -1, MalformedJsonError + } + + offset = nT + 1 + + if len(keys) > 0 { + if offset = searchKeys(data, keys...); offset == -1 { + return offset, KeyPathNotFoundError + } + + // Go to closest value + nO := nextToken(data[offset:]) + if nO == -1 { + return offset, MalformedJsonError + } + + offset += nO + + if data[offset] != '[' { + return offset, MalformedArrayError + } + + offset++ + } + + nO := nextToken(data[offset:]) + if nO == -1 { + return offset, MalformedJsonError + } + + offset += nO + + if data[offset] == ']' { + return offset, nil + } + + for true { + v, t, o, e := Get(data[offset:]) + + if e != nil { + return offset, e + } + + if o == 0 { + break + } + + if t != NotExist { + cb(v, t, offset+o-len(v), e) + } + + if e != nil { + break + } + + offset += o + + skipToToken := nextToken(data[offset:]) + if skipToToken == -1 { + return offset, MalformedArrayError + } + offset += skipToToken + + if data[offset] == ']' { + break + } + + if data[offset] != ',' { + return offset, MalformedArrayError + } + + offset++ + } + + return offset, nil +} + +// ObjectEach iterates over the key-value pairs of a JSON object, invoking a given callback for each such entry +func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error) { + offset := 0 + + // Descend to the desired key, if requested + if len(keys) > 0 { + if off := searchKeys(data, keys...); off == -1 { + return KeyPathNotFoundError + } else { + offset = off + } + } + + // Validate and skip past opening brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedObjectError + } else if offset += off; data[offset] != '{' { + return MalformedObjectError + } else { + offset++ + } + + // Skip to the first token inside the object, or stop if we find the ending brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedJsonError + } else if offset += off; data[offset] == '}' { + return nil + } + + // Loop pre-condition: data[offset] points to what should be either the next entry's key, or the closing brace (if it's anything else, the JSON is malformed) + for offset < len(data) { + // Step 1: find the next key + var key []byte + + // Check what the the next token is: start of string, end of object, or something else (error) + switch data[offset] { + case '"': + offset++ // accept as string and skip opening quote + case '}': + return nil // we found the end of the object; stop and return success + default: + return MalformedObjectError + } + + // Find the end of the key string + var keyEscaped bool + if off, esc := stringEnd(data[offset:]); off == -1 { + return MalformedJsonError + } else { + key, keyEscaped = data[offset:offset+off-1], esc + offset += off + } + + // Unescape the string if needed + if keyEscaped { + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + if keyUnescaped, err := Unescape(key, stackbuf[:]); err != nil { + return MalformedStringEscapeError + } else { + key = keyUnescaped + } + } + + // Step 2: skip the colon + if off := nextToken(data[offset:]); off == -1 { + return MalformedJsonError + } else if offset += off; data[offset] != ':' { + return MalformedJsonError + } else { + offset++ + } + + // Step 3: find the associated value, then invoke the callback + if value, valueType, off, err := Get(data[offset:]); err != nil { + return err + } else if err := callback(key, value, valueType, offset+off); err != nil { // Invoke the callback here! + return err + } else { + offset += off + } + + // Step 4: skip over the next comma to the following token, or stop if we hit the ending brace + if off := nextToken(data[offset:]); off == -1 { + return MalformedArrayError + } else { + offset += off + switch data[offset] { + case '}': + return nil // Stop if we hit the close brace + case ',': + offset++ // Ignore the comma + default: + return MalformedObjectError + } + } + + // Skip to the next token after the comma + if off := nextToken(data[offset:]); off == -1 { + return MalformedArrayError + } else { + offset += off + } + } + + return MalformedObjectError // we shouldn't get here; it's expected that we will return via finding the ending brace +} + +// GetUnsafeString returns the value retrieved by `Get`, use creates string without memory allocation by mapping string to slice memory. It does not handle escape symbols. +func GetUnsafeString(data []byte, keys ...string) (val string, err error) { + v, _, _, e := Get(data, keys...) + + if e != nil { + return "", e + } + + return bytesToString(&v), nil +} + +// GetString returns the value retrieved by `Get`, cast to a string if possible, trying to properly handle escape and utf8 symbols +// If key data type do not match, it will return an error. +func GetString(data []byte, keys ...string) (val string, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return "", e + } + + if t != String { + return "", fmt.Errorf("Value is not a string: %s", string(v)) + } + + // If no escapes return raw content + if bytes.IndexByte(v, '\\') == -1 { + return string(v), nil + } + + return ParseString(v) +} + +// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible. +// The offset is the same as in `Get`. +// If key data type do not match, it will return an error. +func GetFloat(data []byte, keys ...string) (val float64, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return 0, e + } + + if t != Number { + return 0, fmt.Errorf("Value is not a number: %s", string(v)) + } + + return ParseFloat(v) +} + +// GetInt returns the value retrieved by `Get`, cast to a int64 if possible. +// If key data type do not match, it will return an error. +func GetInt(data []byte, keys ...string) (val int64, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return 0, e + } + + if t != Number { + return 0, fmt.Errorf("Value is not a number: %s", string(v)) + } + + return ParseInt(v) +} + +// GetBoolean returns the value retrieved by `Get`, cast to a bool if possible. +// The offset is the same as in `Get`. +// If key data type do not match, it will return error. +func GetBoolean(data []byte, keys ...string) (val bool, err error) { + v, t, _, e := Get(data, keys...) + + if e != nil { + return false, e + } + + if t != Boolean { + return false, fmt.Errorf("Value is not a boolean: %s", string(v)) + } + + return ParseBoolean(v) +} + +// ParseBoolean parses a Boolean ValueType into a Go bool (not particularly useful, but here for completeness) +func ParseBoolean(b []byte) (bool, error) { + switch { + case bytes.Equal(b, trueLiteral): + return true, nil + case bytes.Equal(b, falseLiteral): + return false, nil + default: + return false, MalformedValueError + } +} + +// ParseString parses a String ValueType into a Go string (the main parsing work is unescaping the JSON string) +func ParseString(b []byte) (string, error) { + var stackbuf [unescapeStackBufSize]byte // stack-allocated array for allocation-free unescaping of small strings + if bU, err := Unescape(b, stackbuf[:]); err != nil { + return "", MalformedValueError + } else { + return string(bU), nil + } +} + +// ParseNumber parses a Number ValueType into a Go float64 +func ParseFloat(b []byte) (float64, error) { + if v, err := parseFloat(&b); err != nil { + return 0, MalformedValueError + } else { + return v, nil + } +} + +// ParseInt parses a Number ValueType into a Go int64 +func ParseInt(b []byte) (int64, error) { + if v, ok, overflow := parseInt(b); !ok { + if overflow { + return 0, OverflowIntegerError + } + return 0, MalformedValueError + } else { + return v, nil + } +} diff --git a/vendor/github.com/invopop/jsonschema/.gitignore b/vendor/github.com/invopop/jsonschema/.gitignore new file mode 100644 index 000000000..8ef0e14fc --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.idea/ diff --git a/vendor/github.com/invopop/jsonschema/.golangci.yml b/vendor/github.com/invopop/jsonschema/.golangci.yml new file mode 100644 index 000000000..3dac8a37d --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/.golangci.yml @@ -0,0 +1,70 @@ +run: + tests: true + max-same-issues: 50 + skip-dirs: + - resources + - old + skip-files: + - cmd/protopkg/main.go + +output: + print-issued-lines: false + +linters: + enable: + - gocyclo + - gocritic + - goconst + - dupl + - unconvert + - goimports + - unused + - vetshadow + - nakedret + - errcheck + - revive + - ineffassign + - goconst + - vet + - unparam + - gofmt + +linters-settings: + vet: + check-shadowing: true + use-installed-packages: true + dupl: + threshold: 100 + goconst: + min-len: 8 + min-occurrences: 3 + gocyclo: + min-complexity: 20 + gocritic: + disabled-checks: + - ifElseChain + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' + +issues: + max-per-linter: 0 + max-same: 0 + exclude-use-default: false + exclude: + # Captured by errcheck. + - "^(G104|G204):" + # Very commonly not checked. + - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked' + # Weird error only seen on Kochiku... + - "internal error: no range for" + - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported' + - "composite literal uses unkeyed fields" + - 'declaration of "err" shadows declaration' + - "by other packages, and that stutters" + - "Potential file inclusion via variable" + - "at least one file in a package should have a package comment" + - "bad syntax for struct tag pair" diff --git a/vendor/github.com/invopop/jsonschema/COPYING b/vendor/github.com/invopop/jsonschema/COPYING new file mode 100644 index 000000000..2993ec085 --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/invopop/jsonschema/README.md b/vendor/github.com/invopop/jsonschema/README.md new file mode 100644 index 000000000..1a68a09cb --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/README.md @@ -0,0 +1,373 @@ +# Go JSON Schema Reflection + +[![Lint](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml) +[![Test Go](https://github.com/invopop/jsonschema/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/test.yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/jsonschema)](https://goreportcard.com/report/github.com/invopop/jsonschema) +[![GoDoc](https://godoc.org/github.com/invopop/jsonschema?status.svg)](https://godoc.org/github.com/invopop/jsonschema) +![Latest Tag](https://img.shields.io/github/v/tag/invopop/jsonschema) + +This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection. + +- Supports arbitrarily complex types, including `interface{}`, maps, slices, etc. +- Supports json-schema features such as minLength, maxLength, pattern, format, etc. +- Supports simple string and numeric enums. +- Supports custom property fields via the `jsonschema_extras` struct tag. + +This repository is a fork of the original [jsonschema](https://github.com/alecthomas/jsonschema) by [@alecthomas](https://github.com/alecthomas). At [Invopop](https://invopop.com) we use jsonschema as a cornerstone in our [GOBL library](https://github.com/invopop/gobl), and wanted to be able to continue building and adding features without taking up Alec's time. There have been a few significant changes that probably mean this version is a not compatible with with Alec's: + +- The original was stuck on the draft-04 version of JSON Schema, we've now moved to the latest JSON Schema Draft 2020-12. +- Schema IDs are added automatically from the current Go package's URL in order to be unique, and can be disabled with the `Anonymous` option. +- Support for the `FullyQualifyTypeName` option has been removed. If you have conflicts, you should use multiple schema files with different IDs, set the `DoNotReference` option to true to hide definitions completely, or add your own naming strategy using the `Namer` property. +- Support for `yaml` tags and related options has been dropped for the sake of simplification. There were a [few inconsistencies](https://github.com/invopop/jsonschema/pull/21) around this that have now been fixed. + +## Versions + +This project is still under v0 scheme, as per Go convention, breaking changes are likely. Please pin go modules to version tags or branches, and reach out if you think something can be improved. + +Go version >= 1.18 is required as generics are now being used. + +## Example + +The following Go type: + +```go +type TestUser struct { + ID int `json:"id"` + Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` + Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` + Tags map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` + BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` + YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` + Metadata interface{} `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` + FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` +} +``` + +Results in following JSON Schema: + +```go +jsonschema.Reflect(&TestUser{}) +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/invopop/jsonschema_test/sample-user", + "$ref": "#/$defs/SampleUser", + "$defs": { + "SampleUser": { + "oneOf": [ + { + "required": ["birth_date"], + "title": "date" + }, + { + "required": ["year_of_birth"], + "title": "year" + } + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string", + "title": "the name", + "description": "The name of a friend", + "default": "alex", + "examples": ["joe", "lucy"] + }, + "friends": { + "items": { + "type": "integer" + }, + "type": "array", + "description": "The list of IDs, omitted when empty" + }, + "tags": { + "type": "object", + "a": "b", + "foo": ["bar", "bar1"] + }, + "birth_date": { + "type": "string", + "format": "date-time" + }, + "year_of_birth": { + "type": "string" + }, + "metadata": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array" + } + ] + }, + "fav_color": { + "type": "string", + "enum": ["red", "green", "blue"] + } + }, + "additionalProperties": false, + "type": "object", + "required": ["id", "name"] + } + } +} +``` + +## YAML + +Support for `yaml` tags has now been removed. If you feel very strongly about this, we've opened a discussion to hear your comments: https://github.com/invopop/jsonschema/discussions/28 + +The recommended approach if you need to deal with YAML data is to first convert to JSON. The [invopop/yaml](https://github.com/invopop/yaml) library will make this trivial. + +## Configurable behaviour + +The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector` +instance is created. + +### ExpandedStruct + +If set to `true`, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type. + +eg. + +```go +type GrandfatherType struct { + FamilyName string `json:"family_name" jsonschema:"required"` +} + +type SomeBaseType struct { + SomeBaseProperty int `json:"some_base_property"` + // The jsonschema required tag is nonsensical for private and ignored properties. + // Their presence here tests that the fields *will not* be required in the output + // schema, even if they are tagged required. + somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` + SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` + SomeSchemaIgnoredProperty string `jsonschema:"-,required"` + SomeUntaggedBaseProperty bool `jsonschema:"required"` + someUnexportedUntaggedBaseProperty bool + Grandfather GrandfatherType `json:"grand"` +} +``` + +will output: + +```json +{ + "$schema": "http://json-schema.org/draft/2020-12/schema", + "required": ["some_base_property", "grand", "SomeUntaggedBaseProperty"], + "properties": { + "SomeUntaggedBaseProperty": { + "type": "boolean" + }, + "grand": { + "$schema": "http://json-schema.org/draft/2020-12/schema", + "$ref": "#/definitions/GrandfatherType" + }, + "some_base_property": { + "type": "integer" + } + }, + "type": "object", + "$defs": { + "GrandfatherType": { + "required": ["family_name"], + "properties": { + "family_name": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + } + } +} +``` + +### Using Go Comments + +Writing a good schema with descriptions inside tags can become cumbersome and tedious, especially if you already have some Go comments around your types and field definitions. If you'd like to take advantage of these existing comments, you can use the `AddGoComments(base, path string)` method that forms part of the reflector to parse your go files and automatically generate a dictionary of Go import paths, types, and fields, to individual comments. These will then be used automatically as description fields, and can be overridden with a manual definition if needed. + +Take a simplified example of a User struct which for the sake of simplicity we assume is defined inside this package: + +```go +package main + +// User is used as a base to provide tests for comments. +type User struct { + // Unique sequential identifier. + ID int `json:"id" jsonschema:"required"` + // Name of the user + Name string `json:"name"` +} +``` + +To get the comments provided into your JSON schema, use a regular `Reflector` and add the go code using an import module URL and path. Fully qualified go module paths cannot be determined reliably by the `go/parser` library, so we need to introduce this manually: + +```go +r := new(Reflector) +if err := r.AddGoComments("github.com/invopop/jsonschema", "./"); err != nil { + // deal with error +} +s := r.Reflect(&User{}) +// output +``` + +Expect the results to be similar to: + +```json +{ + "$schema": "http://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/User", + "$defs": { + "User": { + "required": ["id"], + "properties": { + "id": { + "type": "integer", + "description": "Unique sequential identifier." + }, + "name": { + "type": "string", + "description": "Name of the user" + } + }, + "additionalProperties": false, + "type": "object", + "description": "User is used as a base to provide tests for comments." + } + } +} +``` + +### Custom Key Naming + +In some situations, the keys actually used to write files are different from Go structs'. + +This is often the case when writing a configuration file to YAML or JSON from a Go struct, or when returning a JSON response for a Web API: APIs typically use snake_case, while Go uses PascalCase. + +You can pass a `func(string) string` function to `Reflector`'s `KeyNamer` option to map Go field names to JSON key names and reflect the aforementioned transformations, without having to specify `json:"..."` on every struct field. + +For example, consider the following struct + +```go +type User struct { + GivenName string + PasswordSalted []byte `json:"salted_password"` +} +``` + +We can transform field names to snake_case in the generated JSON schema: + +```go +r := new(jsonschema.Reflector) +r.KeyNamer = strcase.SnakeCase // from package github.com/stoewer/go-strcase + +r.Reflect(&User{}) +``` + +Will yield + +```diff + { + "$schema": "http://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/User", + "$defs": { + "User": { + "properties": { +- "GivenName": { ++ "given_name": { + "type": "string" + }, + "salted_password": { + "type": "string", + "contentEncoding": "base64" + } + }, + "additionalProperties": false, + "type": "object", +- "required": ["GivenName", "salted_password"] ++ "required": ["given_name", "salted_password"] + } + } + } +``` + +As you can see, if a field name has a `json:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag. + +### Custom Type Definitions + +Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in your structs that automatically convert for example a string into an object. + +This library will recognize and attempt to call four different methods that help you adjust schemas to your specific needs: + +- `JSONSchema() *Schema` - will prevent auto-generation of the schema so that you can provide your own definition. +- `JSONSchemaExtend(schema *jsonschema.Schema)` - will be called _after_ the schema has been generated, allowing you to add or manipulate the fields easily. +- `JSONSchemaAlias() any` - is called when reflecting the type of object and allows for an alternative to be used instead. +- `JSONSchemaProperty(prop string) any` - will be called for every property inside a struct giving you the chance to provide an alternative object to convert into a schema. + +Note that all of these methods **must** be defined on a non-pointer object for them to be called. + +Take the following simplified example of a `CompactDate` that only includes the Year and Month: + +```go +type CompactDate struct { + Year int + Month int +} + +func (d *CompactDate) UnmarshalJSON(data []byte) error { + if len(data) != 9 { + return errors.New("invalid compact date length") + } + var err error + d.Year, err = strconv.Atoi(string(data[1:5])) + if err != nil { + return err + } + d.Month, err = strconv.Atoi(string(data[7:8])) + if err != nil { + return err + } + return nil +} + +func (d *CompactDate) MarshalJSON() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte('"') + buf.WriteString(fmt.Sprintf("%d-%02d", d.Year, d.Month)) + buf.WriteByte('"') + return buf.Bytes(), nil +} + +func (CompactDate) JSONSchema() *Schema { + return &Schema{ + Type: "string", + Title: "Compact Date", + Description: "Short date that only includes year and month", + Pattern: "^[0-9]{4}-[0-1][0-9]$", + } +} +``` + +The resulting schema generated for this struct would look like: + +```json +{ + "$schema": "http://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/CompactDate", + "$defs": { + "CompactDate": { + "pattern": "^[0-9]{4}-[0-1][0-9]$", + "type": "string", + "title": "Compact Date", + "description": "Short date that only includes year and month" + } + } +} +``` diff --git a/vendor/github.com/invopop/jsonschema/comment_extractor.go b/vendor/github.com/invopop/jsonschema/comment_extractor.go new file mode 100644 index 000000000..e157837af --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/comment_extractor.go @@ -0,0 +1,93 @@ +package jsonschema + +import ( + "fmt" + "io/fs" + gopath "path" + "path/filepath" + "strings" + + "go/ast" + "go/doc" + "go/parser" + "go/token" +) + +// ExtractGoComments will read all the go files contained in the provided path, +// including sub-directories, in order to generate a dictionary of comments +// associated with Types and Fields. The results will be added to the `commentsMap` +// provided in the parameters and expected to be used for Schema "description" fields. +// +// The `go/parser` library is used to extract all the comments and unfortunately doesn't +// have a built-in way to determine the fully qualified name of a package. The `base` paremeter, +// the URL used to import that package, is thus required to be able to match reflected types. +// +// When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase +// only. Field comments, which tend to be much shorter, will include everything. +func ExtractGoComments(base, path string, commentMap map[string]string) error { + fset := token.NewFileSet() + dict := make(map[string][]*ast.Package) + err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) + if err != nil { + return err + } + for _, v := range d { + // paths may have multiple packages, like for tests + k := gopath.Join(base, path) + dict[k] = append(dict[k], v) + } + } + return nil + }) + if err != nil { + return err + } + + for pkg, p := range dict { + for _, f := range p { + gtxt := "" + typ := "" + ast.Inspect(f, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.TypeSpec: + typ = x.Name.String() + if !ast.IsExported(typ) { + typ = "" + } else { + txt := x.Doc.Text() + if txt == "" && gtxt != "" { + txt = gtxt + gtxt = "" + } + txt = doc.Synopsis(txt) + commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) + } + case *ast.Field: + txt := x.Doc.Text() + if txt == "" { + txt = x.Comment.Text() + } + if typ != "" && txt != "" { + for _, n := range x.Names { + if ast.IsExported(n.String()) { + k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) + commentMap[k] = strings.TrimSpace(txt) + } + } + } + case *ast.GenDecl: + // remember for the next type + gtxt = x.Doc.Text() + } + return true + }) + } + } + + return nil +} diff --git a/vendor/github.com/invopop/jsonschema/id.go b/vendor/github.com/invopop/jsonschema/id.go new file mode 100644 index 000000000..73fafb38d --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/id.go @@ -0,0 +1,76 @@ +package jsonschema + +import ( + "errors" + "fmt" + "net/url" + "strings" +) + +// ID represents a Schema ID type which should always be a URI. +// See draft-bhutton-json-schema-00 section 8.2.1 +type ID string + +// EmptyID is used to explicitly define an ID with no value. +const EmptyID ID = "" + +// Validate is used to check if the ID looks like a proper schema. +// This is done by parsing the ID as a URL and checking it has all the +// relevant parts. +func (id ID) Validate() error { + u, err := url.Parse(id.String()) + if err != nil { + return fmt.Errorf("invalid URL: %w", err) + } + if u.Hostname() == "" { + return errors.New("missing hostname") + } + if !strings.Contains(u.Hostname(), ".") { + return errors.New("hostname does not look valid") + } + if u.Path == "" { + return errors.New("path is expected") + } + if u.Scheme != "https" && u.Scheme != "http" { + return errors.New("unexpected schema") + } + return nil +} + +// Anchor sets the anchor part of the schema URI. +func (id ID) Anchor(name string) ID { + b := id.Base() + return ID(b.String() + "#" + name) +} + +// Def adds or replaces a definition identifier. +func (id ID) Def(name string) ID { + b := id.Base() + return ID(b.String() + "#/$defs/" + name) +} + +// Add appends the provided path to the id, and removes any +// anchor data that might be there. +func (id ID) Add(path string) ID { + b := id.Base() + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return ID(b.String() + path) +} + +// Base removes any anchor information from the schema +func (id ID) Base() ID { + s := id.String() + i := strings.LastIndex(s, "#") + if i != -1 { + s = s[0:i] + } + s = strings.TrimRight(s, "/") + return ID(s) +} + +// String provides string version of ID +func (id ID) String() string { + return string(id) +} diff --git a/vendor/github.com/invopop/jsonschema/reflect.go b/vendor/github.com/invopop/jsonschema/reflect.go new file mode 100644 index 000000000..3249c8c36 --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/reflect.go @@ -0,0 +1,1150 @@ +// Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. +// +// If json tags are present on struct fields, they will be used to infer +// property names and if a property is required (omitempty is present). +// +// [1] http://json-schema.org/latest/json-schema-validation.html +package jsonschema + +import ( + "bytes" + "encoding/json" + "net" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +// customSchemaImpl is used to detect if the type provides it's own +// custom Schema Type definition to use instead. Very useful for situations +// where there are custom JSON Marshal and Unmarshal methods. +type customSchemaImpl interface { + JSONSchema() *Schema +} + +// Function to be run after the schema has been generated. +// this will let you modify a schema afterwards +type extendSchemaImpl interface { + JSONSchemaExtend(*Schema) +} + +// If the object to be reflected defines a `JSONSchemaAlias` method, its type will +// be used instead of the original type. +type aliasSchemaImpl interface { + JSONSchemaAlias() any +} + +// If an object to be reflected defines a `JSONSchemaPropertyAlias` method, +// it will be called for each property to determine if another object +// should be used for the contents. +type propertyAliasSchemaImpl interface { + JSONSchemaProperty(prop string) any +} + +var customAliasSchema = reflect.TypeOf((*aliasSchemaImpl)(nil)).Elem() +var customPropertyAliasSchema = reflect.TypeOf((*propertyAliasSchemaImpl)(nil)).Elem() + +var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() +var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() + +// customSchemaGetFieldDocString +type customSchemaGetFieldDocString interface { + GetFieldDocString(fieldName string) string +} + +type customGetFieldDocString func(fieldName string) string + +var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() + +// Reflect reflects to Schema from a value using the default Reflector +func Reflect(v any) *Schema { + return ReflectFromType(reflect.TypeOf(v)) +} + +// ReflectFromType generates root schema using the default Reflector +func ReflectFromType(t reflect.Type) *Schema { + r := &Reflector{} + return r.ReflectFromType(t) +} + +// A Reflector reflects values into a Schema. +type Reflector struct { + // BaseSchemaID defines the URI that will be used as a base to determine Schema + // IDs for models. For example, a base Schema ID of `https://invopop.com/schemas` + // when defined with a struct called `User{}`, will result in a schema with an + // ID set to `https://invopop.com/schemas/user`. + // + // If no `BaseSchemaID` is provided, we'll take the type's complete package path + // and use that as a base instead. Set `Anonymous` to try if you do not want to + // include a schema ID. + BaseSchemaID ID + + // Anonymous when true will hide the auto-generated Schema ID and provide what is + // known as an "anonymous schema". As a rule, this is not recommended. + Anonymous bool + + // AssignAnchor when true will use the original struct's name as an anchor inside + // every definition, including the root schema. These can be useful for having a + // reference to the original struct's name in CamelCase instead of the snake-case used + // by default for URI compatibility. + // + // Anchors do not appear to be widely used out in the wild, so at this time the + // anchors themselves will not be used inside generated schema. + AssignAnchor bool + + // AllowAdditionalProperties will cause the Reflector to generate a schema + // without additionalProperties set to 'false' for all struct types. This means + // the presence of additional keys in JSON objects will not cause validation + // to fail. Note said additional keys will simply be dropped when the + // validated JSON is unmarshaled. + AllowAdditionalProperties bool + + // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema + // that requires any key tagged with `jsonschema:required`, overriding the + // default of requiring any key *not* tagged with `json:,omitempty`. + RequiredFromJSONSchemaTags bool + + // Do not reference definitions. This will remove the top-level $defs map and + // instead cause the entire structure of types to be output in one tree. The + // list of type definitions (`$defs`) will not be included. + DoNotReference bool + + // ExpandedStruct when true will include the reflected type's definition in the + // root as opposed to a definition with a reference. + ExpandedStruct bool + + // FieldNameTag will change the tag used to get field names. json tags are used by default. + FieldNameTag string + + // IgnoredTypes defines a slice of types that should be ignored in the schema, + // switching to just allowing additional properties instead. + IgnoredTypes []any + + // Lookup allows a function to be defined that will provide a custom mapping of + // types to Schema IDs. This allows existing schema documents to be referenced + // by their ID instead of being embedded into the current schema definitions. + // Reflected types will never be pointers, only underlying elements. + Lookup func(reflect.Type) ID + + // Mapper is a function that can be used to map custom Go types to jsonschema schemas. + Mapper func(reflect.Type) *Schema + + // Namer allows customizing of type names. The default is to use the type's name + // provided by the reflect package. + Namer func(reflect.Type) string + + // KeyNamer allows customizing of key names. + // The default is to use the key's name as is, or the json tag if present. + // If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name. + KeyNamer func(string) string + + // AdditionalFields allows adding structfields for a given type + AdditionalFields func(reflect.Type) []reflect.StructField + + // CommentMap is a dictionary of fully qualified go types and fields to comment + // strings that will be used if a description has not already been provided in + // the tags. Types and fields are added to the package path using "." as a + // separator. + // + // Type descriptions should be defined like: + // + // map[string]string{"github.com/invopop/jsonschema.Reflector": "A Reflector reflects values into a Schema."} + // + // And Fields defined as: + // + // map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} + // + // See also: AddGoComments + CommentMap map[string]string +} + +// Reflect reflects to Schema from a value. +func (r *Reflector) Reflect(v any) *Schema { + return r.ReflectFromType(reflect.TypeOf(v)) +} + +// ReflectFromType generates root schema +func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { + if t.Kind() == reflect.Ptr { + t = t.Elem() // re-assign from pointer + } + + name := r.typeName(t) + + s := new(Schema) + definitions := Definitions{} + s.Definitions = definitions + bs := r.reflectTypeToSchemaWithID(definitions, t) + if r.ExpandedStruct { + *s = *definitions[name] + delete(definitions, name) + } else { + *s = *bs + } + + // Attempt to set the schema ID + if !r.Anonymous && s.ID == EmptyID { + baseSchemaID := r.BaseSchemaID + if baseSchemaID == EmptyID { + id := ID("https://" + t.PkgPath()) + if err := id.Validate(); err == nil { + // it's okay to silently ignore URL errors + baseSchemaID = id + } + } + if baseSchemaID != EmptyID { + s.ID = baseSchemaID.Add(ToSnakeCase(name)) + } + } + + s.Version = Version + if !r.DoNotReference { + s.Definitions = definitions + } + + return s +} + +// Available Go defined types for JSON Schema Validation. +// RFC draft-wright-json-schema-validation-00, section 7.3 +var ( + timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 + ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 + uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 +) + +// Byte slices will be encoded as base64 +var byteSliceType = reflect.TypeOf([]byte(nil)) + +// Except for json.RawMessage +var rawMessageType = reflect.TypeOf(json.RawMessage{}) + +// Go code generated from protobuf enum types should fulfil this interface. +type protoEnum interface { + EnumDescriptor() ([]byte, []int) +} + +var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() + +// SetBaseSchemaID is a helper use to be able to set the reflectors base +// schema ID from a string as opposed to then ID instance. +func (r *Reflector) SetBaseSchemaID(id string) { + r.BaseSchemaID = ID(id) +} + +func (r *Reflector) refOrReflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { + id := r.lookupID(t) + if id != EmptyID { + return &Schema{ + Ref: id.String(), + } + } + + // Already added to definitions? + if def := r.refDefinition(definitions, t); def != nil { + return def + } + + return r.reflectTypeToSchemaWithID(definitions, t) +} + +func (r *Reflector) reflectTypeToSchemaWithID(defs Definitions, t reflect.Type) *Schema { + s := r.reflectTypeToSchema(defs, t) + if s != nil { + if r.Lookup != nil { + id := r.Lookup(t) + if id != EmptyID { + s.ID = id + } + } + } + return s +} + +func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { + // only try to reflect non-pointers + if t.Kind() == reflect.Ptr { + return r.refOrReflectTypeToSchema(definitions, t.Elem()) + } + + // Check if the there is an alias method that provides an object + // that we should use instead of this one. + if t.Implements(customAliasSchema) { + v := reflect.New(t) + o := v.Interface().(aliasSchemaImpl) + t = reflect.TypeOf(o.JSONSchemaAlias()) + return r.refOrReflectTypeToSchema(definitions, t) + } + + // Do any pre-definitions exist? + if r.Mapper != nil { + if t := r.Mapper(t); t != nil { + return t + } + } + if rt := r.reflectCustomSchema(definitions, t); rt != nil { + return rt + } + + // Prepare a base to which details can be added + st := new(Schema) + + // jsonpb will marshal protobuf enum options as either strings or integers. + // It will unmarshal either. + if t.Implements(protoEnumType) { + st.OneOf = []*Schema{ + {Type: "string"}, + {Type: "integer"}, + } + return st + } + + // Defined format types for JSON Schema Validation + // RFC draft-wright-json-schema-validation-00, section 7.3 + // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 + if t == ipType { + // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 + st.Type = "string" + st.Format = "ipv4" + return st + } + + switch t.Kind() { + case reflect.Struct: + r.reflectStruct(definitions, t, st) + + case reflect.Slice, reflect.Array: + r.reflectSliceOrArray(definitions, t, st) + + case reflect.Map: + r.reflectMap(definitions, t, st) + + case reflect.Interface: + // empty + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + st.Type = "integer" + + case reflect.Float32, reflect.Float64: + st.Type = "number" + + case reflect.Bool: + st.Type = "boolean" + + case reflect.String: + st.Type = "string" + + default: + panic("unsupported type " + t.String()) + } + + r.reflectSchemaExtend(definitions, t, st) + + // Always try to reference the definition which may have just been created + if def := r.refDefinition(definitions, t); def != nil { + return def + } + + return st +} + +func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) *Schema { + if t.Kind() == reflect.Ptr { + return r.reflectCustomSchema(definitions, t.Elem()) + } + + if t.Implements(customType) { + v := reflect.New(t) + o := v.Interface().(customSchemaImpl) + st := o.JSONSchema() + r.addDefinition(definitions, t, st) + if ref := r.refDefinition(definitions, t); ref != nil { + return ref + } + return st + } + + return nil +} + +func (r *Reflector) reflectSchemaExtend(definitions Definitions, t reflect.Type, s *Schema) *Schema { + if t.Implements(extendType) { + v := reflect.New(t) + o := v.Interface().(extendSchemaImpl) + o.JSONSchemaExtend(s) + if ref := r.refDefinition(definitions, t); ref != nil { + return ref + } + } + + return s +} + +func (r *Reflector) reflectSliceOrArray(definitions Definitions, t reflect.Type, st *Schema) { + if t == rawMessageType { + return + } + + r.addDefinition(definitions, t, st) + + if st.Description == "" { + st.Description = r.lookupComment(t, "") + } + + if t.Kind() == reflect.Array { + l := uint64(t.Len()) + st.MinItems = &l + st.MaxItems = &l + } + if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() { + st.Type = "string" + // NOTE: ContentMediaType is not set here + st.ContentEncoding = "base64" + } else { + st.Type = "array" + st.Items = r.refOrReflectTypeToSchema(definitions, t.Elem()) + } +} + +func (r *Reflector) reflectMap(definitions Definitions, t reflect.Type, st *Schema) { + r.addDefinition(definitions, t, st) + + st.Type = "object" + if st.Description == "" { + st.Description = r.lookupComment(t, "") + } + + switch t.Key().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + st.PatternProperties = map[string]*Schema{ + "^[0-9]+$": r.refOrReflectTypeToSchema(definitions, t.Elem()), + } + st.AdditionalProperties = FalseSchema + return + } + if t.Elem().Kind() != reflect.Interface { + st.AdditionalProperties = r.refOrReflectTypeToSchema(definitions, t.Elem()) + } +} + +// Reflects a struct to a JSON Schema type. +func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type, s *Schema) { + // Handle special types + switch t { + case timeType: // date-time RFC section 7.3.1 + s.Type = "string" + s.Format = "date-time" + return + case uriType: // uri RFC section 7.3.6 + s.Type = "string" + s.Format = "uri" + return + } + + r.addDefinition(definitions, t, s) + s.Type = "object" + s.Properties = NewProperties() + s.Description = r.lookupComment(t, "") + if r.AssignAnchor { + s.Anchor = t.Name() + } + if !r.AllowAdditionalProperties && s.AdditionalProperties == nil { + s.AdditionalProperties = FalseSchema + } + + ignored := false + for _, it := range r.IgnoredTypes { + if reflect.TypeOf(it) == t { + ignored = true + break + } + } + if !ignored { + r.reflectStructFields(s, definitions, t) + } +} + +func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t reflect.Type) { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + return + } + + var getFieldDocString customGetFieldDocString + if t.Implements(customStructGetFieldDocString) { + v := reflect.New(t) + o := v.Interface().(customSchemaGetFieldDocString) + getFieldDocString = o.GetFieldDocString + } + + customPropertyMethod := func(string) any { + return nil + } + if t.Implements(customPropertyAliasSchema) { + v := reflect.New(t) + o := v.Interface().(propertyAliasSchemaImpl) + customPropertyMethod = o.JSONSchemaProperty + } + + handleField := func(f reflect.StructField) { + name, shouldEmbed, required, nullable := r.reflectFieldName(f) + // if anonymous and exported type should be processed recursively + // current type should inherit properties of anonymous one + if name == "" { + if shouldEmbed { + r.reflectStructFields(st, definitions, f.Type) + } + return + } + + // If a JSONSchemaAlias(prop string) method is defined, attempt to use + // the provided object's type instead of the field's type. + var property *Schema + if alias := customPropertyMethod(name); alias != nil { + property = r.refOrReflectTypeToSchema(definitions, reflect.TypeOf(alias)) + } else { + property = r.refOrReflectTypeToSchema(definitions, f.Type) + } + + property.structKeywordsFromTags(f, st, name) + if property.Description == "" { + property.Description = r.lookupComment(t, f.Name) + } + if getFieldDocString != nil { + property.Description = getFieldDocString(f.Name) + } + + if nullable { + property = &Schema{ + OneOf: []*Schema{ + property, + { + Type: "null", + }, + }, + } + } + + st.Properties.Set(name, property) + if required { + st.Required = appendUniqueString(st.Required, name) + } + } + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + handleField(f) + } + if r.AdditionalFields != nil { + if af := r.AdditionalFields(t); af != nil { + for _, sf := range af { + handleField(sf) + } + } + } +} + +func appendUniqueString(base []string, value string) []string { + for _, v := range base { + if v == value { + return base + } + } + return append(base, value) +} + +func (r *Reflector) lookupComment(t reflect.Type, name string) string { + if r.CommentMap == nil { + return "" + } + + n := fullyQualifiedTypeName(t) + if name != "" { + n = n + "." + name + } + + return r.CommentMap[n] +} + +// addDefinition will append the provided schema. If needed, an ID and anchor will also be added. +func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) { + name := r.typeName(t) + if name == "" { + return + } + definitions[name] = s +} + +// refDefinition will provide a schema with a reference to an existing definition. +func (r *Reflector) refDefinition(definitions Definitions, t reflect.Type) *Schema { + if r.DoNotReference { + return nil + } + name := r.typeName(t) + if name == "" { + return nil + } + if _, ok := definitions[name]; !ok { + return nil + } + return &Schema{ + Ref: "#/$defs/" + name, + } +} + +func (r *Reflector) lookupID(t reflect.Type) ID { + if r.Lookup != nil { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return r.Lookup(t) + + } + return EmptyID +} + +func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, propertyName string) { + t.Description = f.Tag.Get("jsonschema_description") + + tags := splitOnUnescapedCommas(f.Tag.Get("jsonschema")) + tags = t.genericKeywords(tags, parent, propertyName) + + switch t.Type { + case "string": + t.stringKeywords(tags) + case "number": + t.numericalKeywords(tags) + case "integer": + t.numericalKeywords(tags) + case "array": + t.arrayKeywords(tags) + case "boolean": + t.booleanKeywords(tags) + } + extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",") + t.extraKeywords(extras) +} + +// read struct tags for generic keywords +func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) []string { //nolint:gocyclo + unprocessed := make([]string, 0, len(tags)) + for _, tag := range tags { + nameValue := strings.SplitN(tag, "=", 2) + if len(nameValue) == 2 { + name, val := nameValue[0], nameValue[1] + switch name { + case "title": + t.Title = val + case "description": + t.Description = val + case "type": + t.Type = val + case "anchor": + t.Anchor = val + case "oneof_required": + var typeFound *Schema + for i := range parent.OneOf { + if parent.OneOf[i].Title == nameValue[1] { + typeFound = parent.OneOf[i] + } + } + if typeFound == nil { + typeFound = &Schema{ + Title: nameValue[1], + Required: []string{}, + } + parent.OneOf = append(parent.OneOf, typeFound) + } + typeFound.Required = append(typeFound.Required, propertyName) + case "anyof_required": + var typeFound *Schema + for i := range parent.AnyOf { + if parent.AnyOf[i].Title == nameValue[1] { + typeFound = parent.AnyOf[i] + } + } + if typeFound == nil { + typeFound = &Schema{ + Title: nameValue[1], + Required: []string{}, + } + parent.AnyOf = append(parent.AnyOf, typeFound) + } + typeFound.Required = append(typeFound.Required, propertyName) + case "oneof_ref": + subSchema := t + if t.Items != nil { + subSchema = t.Items + } + if subSchema.OneOf == nil { + subSchema.OneOf = make([]*Schema, 0, 1) + } + subSchema.Ref = "" + refs := strings.Split(nameValue[1], ";") + for _, r := range refs { + subSchema.OneOf = append(subSchema.OneOf, &Schema{ + Ref: r, + }) + } + case "oneof_type": + if t.OneOf == nil { + t.OneOf = make([]*Schema, 0, 1) + } + t.Type = "" + types := strings.Split(nameValue[1], ";") + for _, ty := range types { + t.OneOf = append(t.OneOf, &Schema{ + Type: ty, + }) + } + case "anyof_ref": + subSchema := t + if t.Items != nil { + subSchema = t.Items + } + if subSchema.AnyOf == nil { + subSchema.AnyOf = make([]*Schema, 0, 1) + } + subSchema.Ref = "" + refs := strings.Split(nameValue[1], ";") + for _, r := range refs { + subSchema.AnyOf = append(subSchema.AnyOf, &Schema{ + Ref: r, + }) + } + case "anyof_type": + if t.AnyOf == nil { + t.AnyOf = make([]*Schema, 0, 1) + } + t.Type = "" + types := strings.Split(nameValue[1], ";") + for _, ty := range types { + t.AnyOf = append(t.AnyOf, &Schema{ + Type: ty, + }) + } + default: + unprocessed = append(unprocessed, tag) + } + } + } + return unprocessed +} + +// read struct tags for boolean type keywords +func (t *Schema) booleanKeywords(tags []string) { + for _, tag := range tags { + nameValue := strings.Split(tag, "=") + if len(nameValue) != 2 { + continue + } + name, val := nameValue[0], nameValue[1] + if name == "default" { + if val == "true" { + t.Default = true + } else if val == "false" { + t.Default = false + } + } + } +} + +// read struct tags for string type keywords +func (t *Schema) stringKeywords(tags []string) { + for _, tag := range tags { + nameValue := strings.SplitN(tag, "=", 2) + if len(nameValue) == 2 { + name, val := nameValue[0], nameValue[1] + switch name { + case "minLength": + t.MinLength = parseUint(val) + case "maxLength": + t.MaxLength = parseUint(val) + case "pattern": + t.Pattern = val + case "format": + switch val { + case "date-time", "email", "hostname", "ipv4", "ipv6", "uri", "uuid": + t.Format = val + } + case "readOnly": + i, _ := strconv.ParseBool(val) + t.ReadOnly = i + case "writeOnly": + i, _ := strconv.ParseBool(val) + t.WriteOnly = i + case "default": + t.Default = val + case "example": + t.Examples = append(t.Examples, val) + case "enum": + t.Enum = append(t.Enum, val) + } + } + } +} + +// read struct tags for numerical type keywords +func (t *Schema) numericalKeywords(tags []string) { + for _, tag := range tags { + nameValue := strings.Split(tag, "=") + if len(nameValue) == 2 { + name, val := nameValue[0], nameValue[1] + switch name { + case "multipleOf": + t.MultipleOf, _ = toJSONNumber(val) + case "minimum": + t.Minimum, _ = toJSONNumber(val) + case "maximum": + t.Maximum, _ = toJSONNumber(val) + case "exclusiveMaximum": + t.ExclusiveMaximum, _ = toJSONNumber(val) + case "exclusiveMinimum": + t.ExclusiveMinimum, _ = toJSONNumber(val) + case "default": + if num, ok := toJSONNumber(val); ok { + t.Default = num + } + case "example": + if num, ok := toJSONNumber(val); ok { + t.Examples = append(t.Examples, num) + } + case "enum": + if num, ok := toJSONNumber(val); ok { + t.Enum = append(t.Enum, num) + } + } + } + } +} + +// read struct tags for object type keywords +// func (t *Type) objectKeywords(tags []string) { +// for _, tag := range tags{ +// nameValue := strings.Split(tag, "=") +// name, val := nameValue[0], nameValue[1] +// switch name{ +// case "dependencies": +// t.Dependencies = val +// break; +// case "patternProperties": +// t.PatternProperties = val +// break; +// } +// } +// } + +// read struct tags for array type keywords +func (t *Schema) arrayKeywords(tags []string) { + var defaultValues []any + + unprocessed := make([]string, 0, len(tags)) + for _, tag := range tags { + nameValue := strings.Split(tag, "=") + if len(nameValue) == 2 { + name, val := nameValue[0], nameValue[1] + switch name { + case "minItems": + t.MinItems = parseUint(val) + case "maxItems": + t.MaxItems = parseUint(val) + case "uniqueItems": + t.UniqueItems = true + case "default": + defaultValues = append(defaultValues, val) + case "format": + t.Items.Format = val + case "pattern": + t.Items.Pattern = val + default: + unprocessed = append(unprocessed, tag) // left for further processing by underlying type + } + } + } + if len(defaultValues) > 0 { + t.Default = defaultValues + } + + if len(unprocessed) == 0 { + // we don't have anything else to process + return + } + + switch t.Items.Type { + case "string": + t.Items.stringKeywords(unprocessed) + case "number": + t.Items.numericalKeywords(unprocessed) + case "integer": + t.Items.numericalKeywords(unprocessed) + case "array": + // explicitly don't support traversal for the [][]..., as it's unclear where the array tags belong + case "boolean": + t.Items.booleanKeywords(unprocessed) + } +} + +func (t *Schema) extraKeywords(tags []string) { + for _, tag := range tags { + nameValue := strings.SplitN(tag, "=", 2) + if len(nameValue) == 2 { + t.setExtra(nameValue[0], nameValue[1]) + } + } +} + +func (t *Schema) setExtra(key, val string) { + if t.Extras == nil { + t.Extras = map[string]any{} + } + if existingVal, ok := t.Extras[key]; ok { + switch existingVal := existingVal.(type) { + case string: + t.Extras[key] = []string{existingVal, val} + case []string: + t.Extras[key] = append(existingVal, val) + case int: + t.Extras[key], _ = strconv.Atoi(val) + case bool: + t.Extras[key] = (val == "true" || val == "t") + } + } else { + switch key { + case "minimum": + t.Extras[key], _ = strconv.Atoi(val) + default: + var x any + if val == "true" { + x = true + } else if val == "false" { + x = false + } else { + x = val + } + t.Extras[key] = x + } + } +} + +func requiredFromJSONTags(tags []string, val *bool) { + if ignoredByJSONTags(tags) { + return + } + + for _, tag := range tags[1:] { + if tag == "omitempty" { + *val = false + return + } + } + *val = true +} + +func requiredFromJSONSchemaTags(tags []string, val *bool) { + if ignoredByJSONSchemaTags(tags) { + return + } + for _, tag := range tags { + if tag == "required" { + *val = true + } + } +} + +func nullableFromJSONSchemaTags(tags []string) bool { + if ignoredByJSONSchemaTags(tags) { + return false + } + for _, tag := range tags { + if tag == "nullable" { + return true + } + } + return false +} + +func ignoredByJSONTags(tags []string) bool { + return tags[0] == "-" +} + +func ignoredByJSONSchemaTags(tags []string) bool { + return tags[0] == "-" +} + +// toJSONNumber converts string to *json.Number. +// It'll aso return whether the number is valid. +func toJSONNumber(s string) (json.Number, bool) { + num := json.Number(s) + if _, err := num.Int64(); err == nil { + return num, true + } + if _, err := num.Float64(); err == nil { + return num, true + } + return json.Number(""), false +} + +func parseUint(num string) *uint64 { + val, err := strconv.ParseUint(num, 10, 64) + if err != nil { + return nil + } + return &val +} + +func (r *Reflector) fieldNameTag() string { + if r.FieldNameTag != "" { + return r.FieldNameTag + } + return "json" +} + +func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) { + jsonTagString := f.Tag.Get(r.fieldNameTag()) + jsonTags := strings.Split(jsonTagString, ",") + + if ignoredByJSONTags(jsonTags) { + return "", false, false, false + } + + schemaTags := strings.Split(f.Tag.Get("jsonschema"), ",") + if ignoredByJSONSchemaTags(schemaTags) { + return "", false, false, false + } + + var required bool + if !r.RequiredFromJSONSchemaTags { + requiredFromJSONTags(jsonTags, &required) + } + requiredFromJSONSchemaTags(schemaTags, &required) + + nullable := nullableFromJSONSchemaTags(schemaTags) + + if f.Anonymous && jsonTags[0] == "" { + // As per JSON Marshal rules, anonymous structs are inherited + if f.Type.Kind() == reflect.Struct { + return "", true, false, false + } + + // As per JSON Marshal rules, anonymous pointer to structs are inherited + if f.Type.Kind() == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct { + return "", true, false, false + } + } + + // Try to determine the name from the different combos + name := f.Name + if jsonTags[0] != "" { + name = jsonTags[0] + } + if !f.Anonymous && f.PkgPath != "" { + // field not anonymous and not export has no export name + name = "" + } else if r.KeyNamer != nil { + name = r.KeyNamer(name) + } + + return name, false, required, nullable +} + +// UnmarshalJSON is used to parse a schema object or boolean. +func (t *Schema) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, []byte("true")) { + *t = *TrueSchema + return nil + } else if bytes.Equal(data, []byte("false")) { + *t = *FalseSchema + return nil + } + type SchemaAlt Schema + aux := &struct { + *SchemaAlt + }{ + SchemaAlt: (*SchemaAlt)(t), + } + return json.Unmarshal(data, aux) +} + +// MarshalJSON is used to serialize a schema object or boolean. +func (t *Schema) MarshalJSON() ([]byte, error) { + if t.boolean != nil { + if *t.boolean { + return []byte("true"), nil + } + return []byte("false"), nil + } + if reflect.DeepEqual(&Schema{}, t) { + // Don't bother returning empty schemas + return []byte("true"), nil + } + type SchemaAlt Schema + b, err := json.Marshal((*SchemaAlt)(t)) + if err != nil { + return nil, err + } + if t.Extras == nil || len(t.Extras) == 0 { + return b, nil + } + m, err := json.Marshal(t.Extras) + if err != nil { + return nil, err + } + if len(b) == 2 { + return m, nil + } + b[len(b)-1] = ',' + return append(b, m[1:]...), nil +} + +func (r *Reflector) typeName(t reflect.Type) string { + if r.Namer != nil { + if name := r.Namer(t); name != "" { + return name + } + } + return t.Name() +} + +// Split on commas that are not preceded by `\`. +// This way, we prevent splitting regexes +func splitOnUnescapedCommas(tagString string) []string { + ret := make([]string, 0) + separated := strings.Split(tagString, ",") + ret = append(ret, separated[0]) + i := 0 + for _, nextTag := range separated[1:] { + if len(ret[i]) == 0 { + ret = append(ret, nextTag) + i++ + continue + } + + if ret[i][len(ret[i])-1] == '\\' { + ret[i] = ret[i][:len(ret[i])-1] + "," + nextTag + } else { + ret = append(ret, nextTag) + i++ + } + } + + return ret +} + +func fullyQualifiedTypeName(t reflect.Type) string { + return t.PkgPath() + "." + t.Name() +} + +// AddGoComments will update the reflectors comment map with all the comments +// found in the provided source directories. See the #ExtractGoComments method +// for more details. +func (r *Reflector) AddGoComments(base, path string) error { + if r.CommentMap == nil { + r.CommentMap = make(map[string]string) + } + return ExtractGoComments(base, path, r.CommentMap) +} diff --git a/vendor/github.com/invopop/jsonschema/schema.go b/vendor/github.com/invopop/jsonschema/schema.go new file mode 100644 index 000000000..2d914b8c8 --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/schema.go @@ -0,0 +1,94 @@ +package jsonschema + +import ( + "encoding/json" + + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +// Version is the JSON Schema version. +var Version = "https://json-schema.org/draft/2020-12/schema" + +// Schema represents a JSON Schema object type. +// RFC draft-bhutton-json-schema-00 section 4.3 +type Schema struct { + // RFC draft-bhutton-json-schema-00 + Version string `json:"$schema,omitempty"` // section 8.1.1 + ID ID `json:"$id,omitempty"` // section 8.2.1 + Anchor string `json:"$anchor,omitempty"` // section 8.2.2 + Ref string `json:"$ref,omitempty"` // section 8.2.3.1 + DynamicRef string `json:"$dynamicRef,omitempty"` // section 8.2.3.2 + Definitions Definitions `json:"$defs,omitempty"` // section 8.2.4 + Comments string `json:"$comment,omitempty"` // section 8.3 + // RFC draft-bhutton-json-schema-00 section 10.2.1 (Sub-schemas with logic) + AllOf []*Schema `json:"allOf,omitempty"` // section 10.2.1.1 + AnyOf []*Schema `json:"anyOf,omitempty"` // section 10.2.1.2 + OneOf []*Schema `json:"oneOf,omitempty"` // section 10.2.1.3 + Not *Schema `json:"not,omitempty"` // section 10.2.1.4 + // RFC draft-bhutton-json-schema-00 section 10.2.2 (Apply sub-schemas conditionally) + If *Schema `json:"if,omitempty"` // section 10.2.2.1 + Then *Schema `json:"then,omitempty"` // section 10.2.2.2 + Else *Schema `json:"else,omitempty"` // section 10.2.2.3 + DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // section 10.2.2.4 + // RFC draft-bhutton-json-schema-00 section 10.3.1 (arrays) + PrefixItems []*Schema `json:"prefixItems,omitempty"` // section 10.3.1.1 + Items *Schema `json:"items,omitempty"` // section 10.3.1.2 (replaces additionalItems) + Contains *Schema `json:"contains,omitempty"` // section 10.3.1.3 + // RFC draft-bhutton-json-schema-00 section 10.3.2 (sub-schemas) + Properties *orderedmap.OrderedMap[string, *Schema] `json:"properties,omitempty"` // section 10.3.2.1 + PatternProperties map[string]*Schema `json:"patternProperties,omitempty"` // section 10.3.2.2 + AdditionalProperties *Schema `json:"additionalProperties,omitempty"` // section 10.3.2.3 + PropertyNames *Schema `json:"propertyNames,omitempty"` // section 10.3.2.4 + // RFC draft-bhutton-json-schema-validation-00, section 6 + Type string `json:"type,omitempty"` // section 6.1.1 + Enum []any `json:"enum,omitempty"` // section 6.1.2 + Const any `json:"const,omitempty"` // section 6.1.3 + MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 + Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 + ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 + Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 + ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 + MaxLength *uint64 `json:"maxLength,omitempty"` // section 6.3.1 + MinLength *uint64 `json:"minLength,omitempty"` // section 6.3.2 + Pattern string `json:"pattern,omitempty"` // section 6.3.3 + MaxItems *uint64 `json:"maxItems,omitempty"` // section 6.4.1 + MinItems *uint64 `json:"minItems,omitempty"` // section 6.4.2 + UniqueItems bool `json:"uniqueItems,omitempty"` // section 6.4.3 + MaxContains *uint64 `json:"maxContains,omitempty"` // section 6.4.4 + MinContains *uint64 `json:"minContains,omitempty"` // section 6.4.5 + MaxProperties *uint64 `json:"maxProperties,omitempty"` // section 6.5.1 + MinProperties *uint64 `json:"minProperties,omitempty"` // section 6.5.2 + Required []string `json:"required,omitempty"` // section 6.5.3 + DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // section 6.5.4 + // RFC draft-bhutton-json-schema-validation-00, section 7 + Format string `json:"format,omitempty"` + // RFC draft-bhutton-json-schema-validation-00, section 8 + ContentEncoding string `json:"contentEncoding,omitempty"` // section 8.3 + ContentMediaType string `json:"contentMediaType,omitempty"` // section 8.4 + ContentSchema *Schema `json:"contentSchema,omitempty"` // section 8.5 + // RFC draft-bhutton-json-schema-validation-00, section 9 + Title string `json:"title,omitempty"` // section 9.1 + Description string `json:"description,omitempty"` // section 9.1 + Default any `json:"default,omitempty"` // section 9.2 + Deprecated bool `json:"deprecated,omitempty"` // section 9.3 + ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 + WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 + Examples []any `json:"examples,omitempty"` // section 9.5 + + Extras map[string]any `json:"-"` + + // Special boolean representation of the Schema - section 4.3.2 + boolean *bool +} + +var ( + // TrueSchema defines a schema with a true value + TrueSchema = &Schema{boolean: &[]bool{true}[0]} + // FalseSchema defines a schema with a false value + FalseSchema = &Schema{boolean: &[]bool{false}[0]} +) + +// Definitions hold schema definitions. +// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 +// RFC draft-wright-json-schema-validation-00, section 5.26 +type Definitions map[string]*Schema diff --git a/vendor/github.com/invopop/jsonschema/utils.go b/vendor/github.com/invopop/jsonschema/utils.go new file mode 100644 index 000000000..ed8edf741 --- /dev/null +++ b/vendor/github.com/invopop/jsonschema/utils.go @@ -0,0 +1,26 @@ +package jsonschema + +import ( + "regexp" + "strings" + + orderedmap "github.com/wk8/go-ordered-map/v2" +) + +var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") +var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") + +// ToSnakeCase converts the provided string into snake case using dashes. +// This is useful for Schema IDs and definitions to be coherent with +// common JSON Schema examples. +func ToSnakeCase(str string) string { + snake := matchFirstCap.ReplaceAllString(str, "${1}-${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}-${2}") + return strings.ToLower(snake) +} + +// NewProperties is a helper method to instantiate a new properties ordered +// map. +func NewProperties() *orderedmap.OrderedMap[string, *Schema] { + return orderedmap.New[string, *Schema]() +} diff --git a/vendor/github.com/loft-sh/vcluster-values/helmvalues/common.go b/vendor/github.com/loft-sh/vcluster-values/helmvalues/common.go deleted file mode 100644 index b5d69c3c3..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/helmvalues/common.go +++ /dev/null @@ -1,405 +0,0 @@ -package helmvalues - -type MonitoringValues struct { - ServiceMonitor ServiceMonitor `json:"serviceMonitor,omitempty"` -} - -type ServiceMonitor struct { - Enabled bool `json:"enabled,omitempty"` -} - -type EmbeddedEtcdValues struct { - Enabled bool `json:"enabled,omitempty"` - MigrateFromEtcd bool `json:"migrateFromEtcd,omitempty"` -} - -type Storage struct { - Persistence bool `json:"persistence,omitempty"` - Size string `json:"size,omitempty"` -} - -type BaseHelm struct { - GlobalAnnotations map[string]string `json:"globalAnnotations,omitempty"` - Pro bool `json:"pro,omitempty"` - ProLicenseSecret string `json:"proLicenseSecret,omitempty"` - Headless bool `json:"headless,omitempty"` - DefaultImageRegistry string `json:"defaultImageRegistry,omitempty"` - Plugin map[string]interface{} `json:"plugin,omitempty"` - Sync SyncValues `json:"sync,omitempty"` - FallbackHostDNS bool `json:"fallbackHostDns,omitempty"` - MapServices MapServices `json:"mapServices,omitempty"` - Proxy ProxyValues `json:"proxy,omitempty"` - Volumes []map[string]interface{} `json:"volumes,omitempty"` - ServiceAccount struct { - Create bool `json:"create,omitempty"` - } `json:"serviceAccount,omitempty"` - WorkloadServiceAccount struct { - Annotations map[string]string `json:"annotations,omitempty"` - } `json:"workloadServiceAccount,omitempty"` - Rbac RBACValues `json:"rbac,omitempty"` - NodeSelector map[string]interface{} `json:"nodeSelector,omitempty"` - Affinity map[string]interface{} `json:"affinity,omitempty"` - PriorityClassName string `json:"priorityClassName,omitempty"` - Tolerations []map[string]interface{} `json:"tolerations,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - PodLabels map[string]string `json:"podLabels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - PodDisruptionBudget PDBValues `json:"podDisruptionBudget,omitempty"` - Service ServiceValues `json:"service,omitempty"` - Ingress IngressValues `json:"ingress,omitempty"` - - SecurityContext map[string]interface{} `json:"securityContext,omitempty"` - PodSecurityContext map[string]interface{} `json:"podSecurityContext,omitempty"` - Openshift struct { - Enable bool `json:"enable,omitempty"` - } `json:"openshift,omitempty"` - Coredns CoreDNSValues `json:"coredns,omitempty"` - Isolation IsolationValues `json:"isolation,omitempty"` - Init InitValues `json:"init,omitempty"` - MultiNamespaceMode EnabledSwitch `json:"multiNamespaceMode,omitempty"` - Telemetry TelemetryValues `json:"telemetry,omitempty"` - NoopSyncer NoopSyncerValues `json:"noopSyncer,omitempty"` - Monitoring MonitoringValues `json:"monitoring,omitempty"` - CentralAdmission AdmissionValues `json:"centralAdmission,omitempty"` -} - -type SyncerValues struct { - ControlPlaneCommonValues - ExtraArgs []string `json:"extraArgs,omitempty"` - Env []map[string]interface{} `json:"env,omitempty"` - LivenessProbe EnabledSwitch `json:"livenessProbe,omitempty"` - ReadinessProbe EnabledSwitch `json:"readinessProbe,omitempty"` - VolumeMounts []map[string]interface{} `json:"volumeMounts,omitempty"` - ExtraVolumeMounts []map[string]interface{} `json:"extraVolumeMounts,omitempty"` - Resources map[string]interface{} `json:"resources,omitempty"` - KubeConfigContextName string `json:"kubeConfigContextName,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` - Replicas uint32 `json:"replicas,omitempty"` - Storage Storage `json:"storage,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` -} - -type SyncValues struct { - Services EnabledSwitch `json:"services,omitempty"` - Configmaps SyncConfigMaps `json:"configmaps,omitempty"` - Secrets SyncSecrets `json:"secrets,omitempty"` - Endpoints EnabledSwitch `json:"endpoints,omitempty"` - Pods SyncPods `json:"pods,omitempty"` - Events EnabledSwitch `json:"events,omitempty"` - PersistentVolumeClaims EnabledSwitch `json:"persistentVolumeClaims,omitempty"` - Ingresses EnabledSwitch `json:"ingresses,omitempty"` - Ingressclasses EnabledSwitch `json:"ingressclasses,omitempty"` - FakeNodes EnabledSwitch `json:"fake-nodes,omitempty"` - FakePersistentvolumes EnabledSwitch `json:"fake-persistentvolumes,omitempty"` - Nodes SyncNodes `json:"nodes,omitempty"` - PersistentVolumes EnabledSwitch `json:"persistentVolumes,omitempty"` - StorageClasses EnabledSwitch `json:"storageClasses,omitempty"` - Hoststorageclasses EnabledSwitch `json:"hoststorageclasses,omitempty"` - Priorityclasses EnabledSwitch `json:"priorityclasses,omitempty"` - Networkpolicies EnabledSwitch `json:"networkpolicies,omitempty"` - Volumesnapshots EnabledSwitch `json:"volumesnapshots,omitempty"` - Poddisruptionbudgets EnabledSwitch `json:"poddisruptionbudgets,omitempty"` - Serviceaccounts EnabledSwitch `json:"serviceaccounts,omitempty"` - Generic SyncGeneric `json:"generic,omitempty"` -} - -type SyncConfigMaps struct { - Enabled bool `json:"enabled,omitempty"` - All bool `json:"all,omitempty"` -} - -type SyncSecrets struct { - Enabled bool `json:"enabled,omitempty"` - All bool `json:"all,omitempty"` -} - -type SyncPods struct { - Enabled bool `json:"enabled,omitempty"` - EphemeralContainers bool `json:"ephemeralContainers,omitempty"` - Status bool `json:"status,omitempty"` -} - -type SyncNodes struct { - FakeKubeletIPs bool `json:"fakeKubeletIPs,omitempty"` - Enabled bool `json:"enabled,omitempty"` - SyncAllNodes bool `json:"syncAllNodes,omitempty"` - NodeSelector string `json:"nodeSelector,omitempty"` - EnableScheduler bool `json:"enableScheduler,omitempty"` - - // Deprecated: should be removed from the chart first - SyncNodeChanges bool `json:"syncNodeChanges,omitempty"` -} - -type SyncGeneric struct { - Config string `json:"config,omitempty"` -} - -type EnabledSwitch struct { - Enabled bool `json:"enabled,omitempty"` -} - -type MapServices struct { - FromVirtual []ServiceMapping `json:"fromVirtual,omitempty"` - FromHost []ServiceMapping `json:"fromHost,omitempty"` -} - -type ServiceMapping struct { - From string `json:"from"` - To string `json:"to"` -} - -type ProxyValues struct { - MetricsServer MetricsProxyServerConfig `json:"metricsServer,omitempty"` -} - -type MetricsProxyServerConfig struct { - Nodes EnabledSwitch `json:"nodes,omitempty"` - Pods EnabledSwitch `json:"pods,omitempty"` -} - -type VClusterValues struct { - Image string `json:"image,omitempty"` - ImagePullPolicy string `json:"imagePullPolicy,omitempty"` - Command []string `json:"command,omitempty"` - BaseArgs []string `json:"baseArgs,omitempty"` - ExtraArgs []string `json:"extraArgs,omitempty"` - ExtraVolumeMounts []map[string]interface{} `json:"extraVolumeMounts,omitempty"` - VolumeMounts []map[string]interface{} `json:"volumeMounts,omitempty"` - Env []map[string]interface{} `json:"env,omitempty"` - Resources map[string]interface{} `json:"resources,omitempty"` - - // this is only provided in context of k0s right now - PriorityClassName string `json:"priorityClassName,omitempty"` -} - -// These should be remove from the chart first as they are deprecated there -type RBACValues struct { - ClusterRole RBACClusterRoleValues `json:"clusterRole,omitempty"` - Role RBACRoleValues `json:"role,omitempty"` -} - -type RBACClusterRoleValues struct { - Create bool `json:"create,omitempty"` - ExtraRules []RBACRule `json:"extraRules,omitempty"` -} - -type RBACRoleValues struct { - Create bool `json:"create,omitempty"` - ExtraRules []RBACRule `json:"extraRules,omitempty"` - ExcludedAPIResources []string `json:"excludedAPIResources,omitempty"` -} - -type RBACRule struct { - // Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. - Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"` - // APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of - // the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. - // +optional - APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"` - // Resources is a list of resources this rule applies to. '*' represents all resources. - // +optional - Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"` - // ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. - // +optional - ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"` - // NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path - // Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. - // Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. - // +optional - NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,5,rep,name=nonResourceURLs"` -} - -type PDBValues struct { - Enabled bool `json:"enabled,omitempty"` - MinAvailable interface{} `json:"minAvailable,omitempty"` - MaxUnavailable interface{} `json:"maxUnavailable,omitempty"` -} - -type ServiceValues struct { - Type string `json:"type,omitempty"` - ExternalIPs []string `json:"externalIPs,omitempty"` - ExternalTrafficPolicy string `json:"externalTrafficPolicy,omitempty"` - LoadBalancerIP string `json:"loadBalancerIP,omitempty"` - LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"` - LoadBalancerClass string `json:"loadBalancerClass,omitempty"` - LoadBalancerAnnotation map[string]string `json:"loadBalancerAnnotations,omitempty"` -} - -type IngressValues struct { - Enabled bool `json:"enabled,omitempty"` - PathType string `json:"pathType,omitempty"` - IngressClassName string `json:"ingressClassName,omitempty"` - Host string `json:"host,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` - TLS []map[string]interface{} `json:"tls,omitempty"` -} - -type CoreDNSValues struct { - Integrated bool `json:"integrated,omitempty"` - Fallback string `json:"fallback,omitempty"` - Plugin CoreDNSPluginValues `json:"plugin,omitempty"` - Enabled bool `json:"enabled,omitempty"` - Replicas uint32 `json:"replicas,omitempty"` - NodeSelector map[string]interface{} `json:"nodeSelector,omitempty"` - Image string `json:"image,omitempty"` - Config string `json:"config,omitempty"` - Service CoreDNSServiceValues `json:"service,omitempty"` - Resources map[string]interface{} `json:"resources,omitempty"` - Manifests string `json:"manifests,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - PodLabels map[string]string `json:"podLabels,omitempty"` -} - -type CoreDNSPluginValues struct { - Enabled bool `json:"enabled,omitempty"` - Config []DNSMappings `json:"config,omitempty"` -} - -type DNSMappings struct { - Record Record `json:"record,omitempty"` - Target Target `json:"target,omitempty"` - AllowedOn []FilterSpec `json:"allowedOn,omitempty"` - ExceptOn []FilterSpec `json:"exceptOn,omitempty"` -} - -type Record struct { - RecordType RecordType `json:"recordType,omitempty"` - FQDN *string `json:"fqdn,omitempty"` - Service *string `json:"service,omitempty"` - Namespace *string `json:"namespace,omitempty"` -} - -type RecordType string -type TargetMode string - -type Target struct { - Mode TargetMode `json:"mode,omitempty"` - VCluster *string `json:"vcluster,omitempty"` - URL *string `json:"url,omitempty"` - Service *string `json:"service,omitempty"` - Namespace *string `json:"namespace,omitempty"` -} - -type FilterSpec struct { - Name string `json:"name,omitempty"` - Namespace string `json:"namespace,omitempty"` - Labels []string `json:"labels,omitempty"` -} - -type CoreDNSServiceValues struct { - Type string `json:"type,omitempty"` - ExternalIPs []string `json:"externalIPs,omitempty"` - ExternalTrafficPolicy string `json:"externalTrafficPolicy,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` -} - -type IsolationValues struct { - Enabled bool `json:"enabled,omitempty"` - Namespace *string `json:"namespace,omitempty"` - PodSecurityStandard string `json:"podSecurityStandard,omitempty"` - NodeProxyPermission EnabledSwitch `json:"nodeProxyPermission,omitempty"` - - ResourceQuota struct { - Enabled bool `json:"enabled,omitempty"` - Quota map[string]interface{} `json:"quota,omitempty"` - ScopeSelector map[string]interface{} `json:"scopeSelector,omitempty"` - Scopes []map[string]interface{} `json:"scopes,omitempty"` - } `json:"resourceQuota,omitempty"` - - LimitRange IsolationLimitRangeValues `json:"limitRange,omitempty"` - NetworkPolicy NetworkPolicyValues `json:"networkPolicy,omitempty"` -} - -type IsolationLimitRangeValues struct { - Enabled bool `json:"enabled,omitempty"` - Default IsolationLimitRangeDefaultValues `json:"default,omitempty"` - DefaultRequest IsolationLimitRangeDefaultValues `json:"defaultRequest,omitempty"` -} - -type IsolationLimitRangeDefaultValues struct { - EphemeralStorage string `json:"ephemeral-storage,omitempty"` - Memory string `json:"memory,omitempty"` - CPU string `json:"cpu,omitempty"` -} - -type NetworkPolicyValues struct { - Enabled bool `json:"enabled,omitempty"` - OutgoingConnections struct { - IPBlock struct { - CIDR string `json:"cidr,omitempty"` - Except []string `json:"except,omitempty"` - } `json:"ipBlock,omitempty"` - } `json:"outgoingConnections,omitempty"` -} - -type InitValues struct { - Manifests string `json:"manifests,omitempty"` - ManifestsTemplate string `json:"manifestsTemplate,omitempty"` - Helm []InitHelmCharts `json:"helm,omitempty"` -} - -type InitHelmCharts struct { - Bundle string `json:"bundle,omitempty"` - Chart struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Repo string `json:"repo,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Insecure bool `json:"insecure,omitempty"` - } `json:"chart,omitempty"` - Release struct { - ReleaseName string `json:"releaseName,omitempty"` - ReleaseNamespace string `json:"releaseNamespace,omitempty"` - Timeout uint32 `json:"timeout,omitempty"` - } `json:"release,omitempty"` - Values string `json:"values,omitempty"` - ValuesTemplate string `json:"valuesTemplate,omitempty"` -} - -type TelemetryValues struct { - Disabled bool `json:"disabled,omitempty"` - InstanceCreator string `json:"instanceCreator,omitempty"` - PlatformUserID string `json:"platformUserID,omitempty"` - PlatformInstanceID string `json:"platformInstanceID,omitempty"` - MachineID string `json:"machineID,omitempty"` -} - -type NoopSyncerValues struct { - Enabled bool `json:"enabled,omitempty"` - Synck8sService bool `json:"synck8SService,omitempty"` - Secret struct { - ServerCaCert string `json:"serverCaCert,omitempty"` - ServerCaKey string `json:"serverCaKey,omitempty"` - ClientCaCert string `json:"clientCaCert,omitempty"` - RequestHeaderCaCert string `json:"requestHeaderCaCert,omitempty"` - KubeConfig string `json:"kubeConfig,omitempty"` - } -} - -type AdmissionValues struct { - ValidatingWebhooks []map[string]interface{} `json:"validatingWebhooks,omitempty"` - MutatingWebhooks []map[string]interface{} `json:"mutatingWebhooks,omitempty"` -} - -type ControlPlaneCommonValues struct { - Image string `json:"image,omitempty"` - ImagePullPolicy string `json:"imagePullPolicy,omitempty"` -} - -type SyncerExORCommonValues struct { - ExtraArgs []string `json:"extraArgs,omitempty"` - Resources map[string]interface{} `json:"resources,omitempty"` -} - -type CommonValues struct { - Volumes []map[string]interface{} `json:"volumes,omitempty"` - PriorityClassName string `json:"priorityClassName,omitempty"` - NodeSelector map[string]interface{} `json:"nodeSelector,omitempty"` - Affinity map[string]interface{} `json:"affinity,omitempty"` - Tolerations []map[string]interface{} `json:"tolerations,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - PodLabels map[string]string `json:"podLabels,omitempty"` -} diff --git a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k0s.go b/vendor/github.com/loft-sh/vcluster-values/helmvalues/k0s.go deleted file mode 100644 index 36faf4da8..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k0s.go +++ /dev/null @@ -1,9 +0,0 @@ -package helmvalues - -type K0s struct { - BaseHelm - AutoDeletePersistentVolumeClaims bool `json:"autoDeletePersistentVolumeClaims,omitempty"` - VCluster VClusterValues `json:"vcluster,omitempty"` - Syncer SyncerValues `json:"syncer,omitempty"` - EmbeddedEtcd EmbeddedEtcdValues `json:"embeddedEtcd,omitempty"` -} diff --git a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k3s.go b/vendor/github.com/loft-sh/vcluster-values/helmvalues/k3s.go deleted file mode 100644 index e12718d86..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k3s.go +++ /dev/null @@ -1,23 +0,0 @@ -package helmvalues - -type K3s struct { - BaseHelm - AutoDeletePersistentVolumeClaims bool `json:"autoDeletePersistentVolumeClaims,omitempty"` - K3sToken string `json:"k3sToken,omitempty"` - EmbeddedEtcd EmbeddedEtcdValues `json:"embeddedEtcd,omitempty"` - Etcd K3SEtcdValues `json:"etcd,omitempty"` - VCluster VClusterValues `json:"vcluster,omitempty"` - Syncer SyncerValues `json:"syncer,omitempty"` -} - -type K3SEtcdValues struct { - Enabled bool `json:"enabled,omitempty"` - Migrate bool `json:"migrate,omitempty"` - - CommonValues - SyncerExORCommonValues - ControlPlaneCommonValues - Storage Storage `json:"storage,omitempty"` - SecurityContext map[string]interface{} `json:"securityContext,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` -} diff --git a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k8s.go b/vendor/github.com/loft-sh/vcluster-values/helmvalues/k8s.go deleted file mode 100644 index b56d18c26..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/helmvalues/k8s.go +++ /dev/null @@ -1,51 +0,0 @@ -package helmvalues - -type K8s struct { - BaseHelm - Syncer K8sSyncerValues `json:"syncer,omitempty"` - API APIServerValues `json:"api,omitempty"` - Controller ControllerValues `json:"controller,omitempty"` - Scheduler SchedulerValues `json:"scheduler,omitempty"` - Etcd EtcdValues `json:"etcd,omitempty"` - EmbeddedEtcd EmbeddedEtcdValues `json:"embeddedEtcd,omitempty"` -} - -type K8sSyncerValues struct { - SyncerValues - CommonValues - SecurityContext map[string]interface{} `json:"securityContext,omitempty"` - PodSecurityContext map[string]interface{} `json:"podSecurityContext,omitempty"` -} - -type APIServerValues struct { - SyncerExORCommonValues - ControlPlaneCommonValues - SecurityContext map[string]interface{} `json:"securityContext,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` -} - -type ControllerValues struct { - SyncerExORCommonValues - ControlPlaneCommonValues -} - -type SchedulerValues struct { - SyncerExORCommonValues - ControlPlaneCommonValues - Disabled bool `json:"disabled,omitempty"` -} - -type EtcdValues struct { - // Disabled is allowed for k8s & eks - Disabled bool `json:"disabled,omitempty"` - CommonValues - SyncerExORCommonValues - ControlPlaneCommonValues - SecurityContext map[string]interface{} `json:"securityContext,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` - AutoDeletePersistentVolumeClaims bool `json:"autoDeletePersistentVolumeClaims,omitempty"` - Replicas uint32 `json:"replicas,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` - Storage Storage `json:"storage,omitempty"` -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/default.go b/vendor/github.com/loft-sh/vcluster-values/values/default.go deleted file mode 100644 index 1f805e77e..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/default.go +++ /dev/null @@ -1,20 +0,0 @@ -package values - -import ( - "github.com/go-logr/logr" -) - -func GetDefaultReleaseValues(chartOptions *ChartOptions, log logr.Logger) (string, error) { - switch chartOptions.ChartName { - case K3SChart: - return getDefaultK3SReleaseValues(chartOptions, log) - case K0SChart: - return getDefaultK0SReleaseValues(chartOptions, log) - case K8SChart: - return getDefaultK8SReleaseValues(chartOptions, log) - case EKSChart: - return getDefaultEKSReleaseValues(chartOptions, log) - } - - return "", nil -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/eks.go b/vendor/github.com/loft-sh/vcluster-values/values/eks.go deleted file mode 100644 index baf58da7e..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/eks.go +++ /dev/null @@ -1,89 +0,0 @@ -package values - -import ( - "strings" - - "github.com/go-logr/logr" -) - -var EKSAPIVersionMap = map[string]string{ - "1.28": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.28.2-eks-1-28-6", - "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.27.6-eks-1-27-13", - "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.26.9-eks-1-26-19", - "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.25.14-eks-1-25-23", -} - -var EKSControllerVersionMap = map[string]string{ - "1.28": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.28.2-eks-1-28-6", - "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.27.6-eks-1-27-13", - "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.26.9-eks-1-26-19", - "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.25.14-eks-1-25-23", -} - -var EKSEtcdVersionMap = map[string]string{ - "1.28": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.9-eks-1-28-6", - "1.27": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-27-13", - "1.26": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-26-19", - "1.25": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-25-23", -} - -var EKSCoreDNSVersionMap = map[string]string{ - "1.28": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-28-6", - "1.27": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-27-13", - "1.26": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-26-19", - "1.25": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-25-23", -} - -func getDefaultEKSReleaseValues(chartOptions *ChartOptions, log logr.Logger) (string, error) { - apiImage := "" - controllerImage := "" - etcdImage := "" - corednsImage := "" - if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } - - var ok bool - apiImage = EKSAPIVersionMap[serverVersionString] - controllerImage = EKSControllerVersionMap[serverVersionString] - etcdImage = EKSEtcdVersionMap[serverVersionString] - corednsImage, ok = EKSCoreDNSVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 28 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.28", "serverVersion", serverVersionString) - apiImage = EKSAPIVersionMap["1.28"] - controllerImage = EKSControllerVersionMap["1.28"] - etcdImage = EKSEtcdVersionMap["1.28"] - corednsImage = EKSCoreDNSVersionMap["1.28"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.25", "serverVersion", serverVersionString) - apiImage = EKSAPIVersionMap["1.25"] - controllerImage = EKSControllerVersionMap["1.25"] - etcdImage = EKSEtcdVersionMap["1.25"] - corednsImage = EKSCoreDNSVersionMap["1.25"] - } - } - } - - // build values - values := "" - if apiImage != "" { - values = `api: - image: ##API_IMAGE## -controller: - image: ##CONTROLLER_IMAGE## -etcd: - image: ##ETCD_IMAGE## -coredns: - image: ##COREDNS_IMAGE## -` - values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) - values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) - values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) - values = strings.ReplaceAll(values, "##COREDNS_IMAGE##", corednsImage) - } - return addCommonReleaseValues(values, chartOptions) -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/k0s.go b/vendor/github.com/loft-sh/vcluster-values/values/k0s.go deleted file mode 100644 index d76655af0..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/k0s.go +++ /dev/null @@ -1,47 +0,0 @@ -package values - -import ( - "strings" - - "github.com/go-logr/logr" -) - -var K0SVersionMap = map[string]string{ - "1.29": "k0sproject/k0s:v1.29.1-k0s.0", - "1.28": "k0sproject/k0s:v1.28.2-k0s.0", - "1.27": "k0sproject/k0s:v1.27.6-k0s.0", - "1.26": "k0sproject/k0s:v1.26.9-k0s.0", -} - -func getDefaultK0SReleaseValues(chartOptions *ChartOptions, log logr.Logger) (string, error) { - image := "" - if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } - - var ok bool - image, ok = K0SVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 29 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.29", "serverVersion", serverVersionString) - image = K0SVersionMap["1.29"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.26", "serverVersion", serverVersionString) - image = K0SVersionMap["1.26"] - } - } - } - - // build values - values := "" - if image != "" { - values = `vcluster: - image: ##IMAGE## -` - values = strings.ReplaceAll(values, "##IMAGE##", image) - } - return addCommonReleaseValues(values, chartOptions) -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/k3s.go b/vendor/github.com/loft-sh/vcluster-values/values/k3s.go deleted file mode 100644 index 4431872da..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/k3s.go +++ /dev/null @@ -1,156 +0,0 @@ -package values - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/go-logr/logr" -) - -var K3SVersionMap = map[string]string{ - "1.29": "rancher/k3s:v1.29.0-k3s1", - "1.28": "rancher/k3s:v1.28.5-k3s1", - "1.27": "rancher/k3s:v1.27.9-k3s1", - "1.26": "rancher/k3s:v1.26.12-k3s1", -} - -var replaceRegEx = regexp.MustCompile("[^0-9]+") - -func getDefaultK3SReleaseValues(chartOptions *ChartOptions, log logr.Logger) (string, error) { - var ( - image = chartOptions.K3SImage - serverVersionString string - serverMinorInt int - err error - ) - - if image == "" && chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { - serverVersionString = GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err = GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } - - var ok bool - image, ok = K3SVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 29 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.28", "serverVersion", serverVersionString) - image = K3SVersionMap["1.29"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.26", "serverVersion", serverVersionString) - image = K3SVersionMap["1.26"] - } - } - } - - // build values - values := "" - if image != "" { - values = `vcluster: - image: ##IMAGE## -` - values = strings.ReplaceAll(values, "##IMAGE##", image) - } - if chartOptions.Isolate { - values += ` -securityContext: - runAsUser: 12345 - runAsNonRoot: true` - } - return addCommonReleaseValues(values, chartOptions) -} - -func addCommonReleaseValues(values string, chartOptions *ChartOptions) (string, error) { - if chartOptions.CIDR != "" { - values += ` -serviceCIDR: ##CIDR##` - values = strings.ReplaceAll(values, "##CIDR##", chartOptions.CIDR) - } - - if chartOptions.DisableIngressSync { - values += ` -syncer: - extraArgs: ["--disable-sync-resources=ingresses"]` - } - - if chartOptions.CreateClusterRole { - values += ` -rbac: - clusterRole: - create: true` - } - - if chartOptions.Expose { - values += ` -service: - type: LoadBalancer` - } else if chartOptions.NodePort { - values += ` -service: - type: NodePort` - } - - if chartOptions.SyncNodes { - values += ` -sync: - nodes: - enabled: true` - } - - if chartOptions.Isolate { - values += ` -isolation: - enabled: true` - } - - if chartOptions.DisableTelemetry { - values += ` -telemetry: - disabled: true` - } else if chartOptions.InstanceCreatorType != "" { - values += ` -telemetry: - disabled: false - instanceCreator: "##INSTANCE_CREATOR##" - platformUserID: "##PLATFORM_USER_ID##" - platformInstanceID: "##PLATFORM_INSTANCE_ID##" - machineID: "##MACHINE_ID##"` - values = strings.ReplaceAll(values, "##INSTANCE_CREATOR##", chartOptions.InstanceCreatorType) - values = strings.ReplaceAll(values, "##PLATFORM_USER_ID##", chartOptions.PlatformUserID) - values = strings.ReplaceAll(values, "##PLATFORM_INSTANCE_ID##", chartOptions.PlatformInstanceID) - values = strings.ReplaceAll(values, "##MACHINE_ID##", chartOptions.MachineID) - } - - values = strings.TrimSpace(values) - return values, nil -} - -func ParseKubernetesVersionInfo(versionStr string) (*Version, error) { - if versionStr[0] == 'v' { - versionStr = versionStr[1:] - } - - splittedVersion := strings.Split(versionStr, ".") - if len(splittedVersion) != 2 && len(splittedVersion) != 3 { - return nil, fmt.Errorf("unrecognized kubernetes version %s, please use format vX.X", versionStr) - } - - major := splittedVersion[0] - minor := splittedVersion[1] - - return &Version{ - Major: major, - Minor: minor, - }, nil -} - -func GetKubernetesVersion(serverVersion Version) string { - return replaceRegEx.ReplaceAllString(serverVersion.Major, "") + "." + replaceRegEx.ReplaceAllString(serverVersion.Minor, "") -} - -func GetKubernetesMinorVersion(serverVersion Version) (int, error) { - return strconv.Atoi(replaceRegEx.ReplaceAllString(serverVersion.Minor, "")) -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/k8s.go b/vendor/github.com/loft-sh/vcluster-values/values/k8s.go deleted file mode 100644 index 5d89c7f7f..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/k8s.go +++ /dev/null @@ -1,89 +0,0 @@ -package values - -import ( - "strings" - - "github.com/go-logr/logr" -) - -var K8SAPIVersionMap = map[string]string{ - "1.29": "registry.k8s.io/kube-apiserver:v1.29.0", - "1.28": "registry.k8s.io/kube-apiserver:v1.28.4", - "1.27": "registry.k8s.io/kube-apiserver:v1.27.8", - "1.26": "registry.k8s.io/kube-apiserver:v1.26.11", -} - -var K8SControllerVersionMap = map[string]string{ - "1.29": "registry.k8s.io/kube-controller-manager:v1.29.0", - "1.28": "registry.k8s.io/kube-controller-manager:v1.28.4", - "1.27": "registry.k8s.io/kube-controller-manager:v1.27.8", - "1.26": "registry.k8s.io/kube-controller-manager:v1.26.11", -} - -var K8SSchedulerVersionMap = map[string]string{ - "1.29": "registry.k8s.io/kube-scheduler:v1.29.0", - "1.28": "registry.k8s.io/kube-scheduler:v1.28.4", - "1.27": "registry.k8s.io/kube-scheduler:v1.27.8", - "1.26": "registry.k8s.io/kube-scheduler:v1.26.11", -} - -var K8SEtcdVersionMap = map[string]string{ - "1.29": "registry.k8s.io/etcd:3.5.10-0", - "1.28": "registry.k8s.io/etcd:3.5.9-0", - "1.27": "registry.k8s.io/etcd:3.5.7-0", - "1.26": "registry.k8s.io/etcd:3.5.6-0", -} - -func getDefaultK8SReleaseValues(chartOptions *ChartOptions, log logr.Logger) (string, error) { - apiImage := "" - controllerImage := "" - etcdImage := "" - schedulerImage := "" - if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } - - var ok bool - apiImage = K8SAPIVersionMap[serverVersionString] - controllerImage = K8SControllerVersionMap[serverVersionString] - schedulerImage = K8SSchedulerVersionMap[serverVersionString] - etcdImage, ok = K8SEtcdVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 29 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.29", "serverVersion", serverVersionString) - apiImage = K8SAPIVersionMap["1.29"] - controllerImage = K8SControllerVersionMap["1.29"] - schedulerImage = K8SSchedulerVersionMap["1.29"] - etcdImage = K8SEtcdVersionMap["1.29"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.26", "serverVersion", serverVersionString) - apiImage = K8SAPIVersionMap["1.26"] - controllerImage = K8SControllerVersionMap["1.26"] - schedulerImage = K8SSchedulerVersionMap["1.26"] - etcdImage = K8SEtcdVersionMap["1.26"] - } - } - } - - // build values - values := "" - if apiImage != "" { - values = `api: - image: ##API_IMAGE## -scheduler: - image: ##SCHEDULER_IMAGE## -controller: - image: ##CONTROLLER_IMAGE## -etcd: - image: ##ETCD_IMAGE## -` - values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) - values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) - values = strings.ReplaceAll(values, "##SCHEDULER_IMAGE##", schedulerImage) - values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) - } - return addCommonReleaseValues(values, chartOptions) -} diff --git a/vendor/github.com/loft-sh/vcluster-values/values/values.go b/vendor/github.com/loft-sh/vcluster-values/values/values.go deleted file mode 100644 index 1be515f7b..000000000 --- a/vendor/github.com/loft-sh/vcluster-values/values/values.go +++ /dev/null @@ -1,36 +0,0 @@ -package values - -const ( - K3SChart = "vcluster" - K0SChart = "vcluster-k0s" - K8SChart = "vcluster-k8s" - EKSChart = "vcluster-eks" -) - -// ChartOptions holds the chart options -type ChartOptions struct { - ChartName string - ChartRepo string - ChartVersion string - CIDR string - CreateClusterRole bool - DisableIngressSync bool - Expose bool - NodePort bool - SyncNodes bool - K3SImage string - Isolate bool - KubernetesVersion Version - Pro bool - - DisableTelemetry bool - InstanceCreatorType string - MachineID string - PlatformInstanceID string - PlatformUserID string -} - -type Version struct { - Major string - Minor string -} diff --git a/vendor/github.com/wk8/go-ordered-map/v2/.gitignore b/vendor/github.com/wk8/go-ordered-map/v2/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml b/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml new file mode 100644 index 000000000..2417df10d --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/.golangci.yml @@ -0,0 +1,80 @@ +run: + tests: false + +linters: + disable-all: true + enable: + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - decorder + - depguard + - dogsled + - dupl + - durationcheck + - errcheck + - errchkjson + # FIXME: commented out as it crashes with 1.18 for now + # - errname + - errorlint + - exportloopref + - forbidigo + - funlen + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godox + - gofmt + - gofumpt + - goheader + - goimports + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - grouper + - ifshort + - importas + - ineffassign + - lll + - maintidx + - makezero + - misspell + - nakedret + - nilerr + - nilnil + - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + - promlinter + # FIXME: doesn't support 1.18 yet + # - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - structcheck + - stylecheck + - tagliatelle + - tenv + - testpackage + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - varcheck + - varnamelen + - wastedassign + - whitespace diff --git a/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md b/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md new file mode 100644 index 000000000..f27126f84 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +[comment]: # (Changes since last release go here) + +## 2.1.8 - Jun 27th 2023 + +* Added support for YAML serialization/deserialization + +## 2.1.7 - Apr 13th 2023 + +* Renamed test_utils.go to utils_test.go + +## 2.1.6 - Feb 15th 2023 + +* Added `GetAndMoveToBack()` and `GetAndMoveToFront()` methods + +## 2.1.5 - Dec 13th 2022 + +* Added `Value()` method + +## 2.1.4 - Dec 12th 2022 + +* Fixed a bug with UTF-8 special characters in JSON keys + +## 2.1.3 - Dec 11th 2022 + +* Added support for JSON marshalling/unmarshalling of wrapper of primitive types + +## 2.1.2 - Dec 10th 2022 +* Allowing to pass options to `New`, to give a capacity hint, or initial data +* Allowing to deserialize nested ordered maps from JSON without having to explicitly instantiate them +* Added the `AddPairs` method + +## 2.1.1 - Dec 9th 2022 +* Fixing a bug with JSON marshalling + +## 2.1.0 - Dec 7th 2022 +* Added support for JSON serialization/deserialization diff --git a/vendor/github.com/loft-sh/vcluster-values/LICENSE b/vendor/github.com/wk8/go-ordered-map/v2/LICENSE similarity index 99% rename from vendor/github.com/loft-sh/vcluster-values/LICENSE rename to vendor/github.com/wk8/go-ordered-map/v2/LICENSE index 261eeb9e9..8dada3eda 100644 --- a/vendor/github.com/loft-sh/vcluster-values/LICENSE +++ b/vendor/github.com/wk8/go-ordered-map/v2/LICENSE @@ -178,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" + boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/github.com/wk8/go-ordered-map/v2/Makefile b/vendor/github.com/wk8/go-ordered-map/v2/Makefile new file mode 100644 index 000000000..6e0e18a1b --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/Makefile @@ -0,0 +1,32 @@ +.DEFAULT_GOAL := all + +.PHONY: all +all: test_with_fuzz lint + +# the TEST_FLAGS env var can be set to eg run only specific tests +TEST_COMMAND = go test -v -count=1 -race -cover $(TEST_FLAGS) + +.PHONY: test +test: + $(TEST_COMMAND) + +.PHONY: bench +bench: + go test -bench=. + +FUZZ_TIME ?= 10s + +# see https://github.com/golang/go/issues/46312 +# and https://stackoverflow.com/a/72673487/4867444 +# if we end up having more fuzz tests +.PHONY: test_with_fuzz +test_with_fuzz: + $(TEST_COMMAND) -fuzz=FuzzRoundTripJSON -fuzztime=$(FUZZ_TIME) + $(TEST_COMMAND) -fuzz=FuzzRoundTripYAML -fuzztime=$(FUZZ_TIME) + +.PHONY: fuzz +fuzz: test_with_fuzz + +.PHONY: lint +lint: + golangci-lint run diff --git a/vendor/github.com/wk8/go-ordered-map/v2/README.md b/vendor/github.com/wk8/go-ordered-map/v2/README.md new file mode 100644 index 000000000..b02894443 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/README.md @@ -0,0 +1,154 @@ +[![Go Reference](https://pkg.go.dev/badge/github.com/wk8/go-ordered-map/v2.svg)](https://pkg.go.dev/github.com/wk8/go-ordered-map/v2) +[![Build Status](https://circleci.com/gh/wk8/go-ordered-map.svg?style=svg)](https://app.circleci.com/pipelines/github/wk8/go-ordered-map) + +# Golang Ordered Maps + +Same as regular maps, but also remembers the order in which keys were inserted, akin to [Python's `collections.OrderedDict`s](https://docs.python.org/3.7/library/collections.html#ordereddict-objects). + +It offers the following features: +* optimal runtime performance (all operations are constant time) +* optimal memory usage (only one copy of values, no unnecessary memory allocation) +* allows iterating from newest or oldest keys indifferently, without memory copy, allowing to `break` the iteration, and in time linear to the number of keys iterated over rather than the total length of the ordered map +* supports any generic types for both keys and values. If you're running go < 1.18, you can use [version 1](https://github.com/wk8/go-ordered-map/tree/v1) that takes and returns generic `interface{}`s instead of using generics +* idiomatic API, akin to that of [`container/list`](https://golang.org/pkg/container/list) +* support for JSON and YAML marshalling + +## Documentation + +[The full documentation is available on pkg.go.dev](https://pkg.go.dev/github.com/wk8/go-ordered-map/v2). + +## Installation +```bash +go get -u github.com/wk8/go-ordered-map/v2 +``` + +Or use your favorite golang vendoring tool! + +## Supported go versions + +Go >= 1.18 is required to use version >= 2 of this library, as it uses generics. + +If you're running go < 1.18, you can use [version 1](https://github.com/wk8/go-ordered-map/tree/v1) instead. + +## Example / usage + +```go +package main + +import ( + "fmt" + + "github.com/wk8/go-ordered-map/v2" +) + +func main() { + om := orderedmap.New[string, string]() + + om.Set("foo", "bar") + om.Set("bar", "baz") + om.Set("coucou", "toi") + + fmt.Println(om.Get("foo")) // => "bar", true + fmt.Println(om.Get("i dont exist")) // => "", false + + // iterating pairs from oldest to newest: + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + fmt.Printf("%s => %s\n", pair.Key, pair.Value) + } // prints: + // foo => bar + // bar => baz + // coucou => toi + + // iterating over the 2 newest pairs: + i := 0 + for pair := om.Newest(); pair != nil; pair = pair.Prev() { + fmt.Printf("%s => %s\n", pair.Key, pair.Value) + i++ + if i >= 2 { + break + } + } // prints: + // coucou => toi + // bar => baz +} +``` + +An `OrderedMap`'s keys must implement `comparable`, and its values can be anything, for example: + +```go +type myStruct struct { + payload string +} + +func main() { + om := orderedmap.New[int, *myStruct]() + + om.Set(12, &myStruct{"foo"}) + om.Set(1, &myStruct{"bar"}) + + value, present := om.Get(12) + if !present { + panic("should be there!") + } + fmt.Println(value.payload) // => foo + + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + fmt.Printf("%d => %s\n", pair.Key, pair.Value.payload) + } // prints: + // 12 => foo + // 1 => bar +} +``` + +Also worth noting that you can provision ordered maps with a capacity hint, as you would do by passing an optional hint to `make(map[K]V, capacity`): +```go +om := orderedmap.New[int, *myStruct](28) +``` + +You can also pass in some initial data to store in the map: +```go +om := orderedmap.New[int, string](orderedmap.WithInitialData[int, string]( + orderedmap.Pair[int, string]{ + Key: 12, + Value: "foo", + }, + orderedmap.Pair[int, string]{ + Key: 28, + Value: "bar", + }, +)) +``` + +`OrderedMap`s also support JSON serialization/deserialization, and preserves order: + +```go +// serialization +data, err := json.Marshal(om) +... + +// deserialization +om := orderedmap.New[string, string]() // or orderedmap.New[int, any](), or any type you expect +err := json.Unmarshal(data, &om) +... +``` + +Similarly, it also supports YAML serialization/deserialization using the yaml.v3 package, which also preserves order: + +```go +// serialization +data, err := yaml.Marshal(om) +... + +// deserialization +om := orderedmap.New[string, string]() // or orderedmap.New[int, any](), or any type you expect +err := yaml.Unmarshal(data, &om) +... +``` + +## Alternatives + +There are several other ordered map golang implementations out there, but I believe that at the time of writing none of them offer the same functionality as this library; more specifically: +* [iancoleman/orderedmap](https://github.com/iancoleman/orderedmap) only accepts `string` keys, its `Delete` operations are linear +* [cevaris/ordered_map](https://github.com/cevaris/ordered_map) uses a channel for iterations, and leaks goroutines if the iteration is interrupted before fully traversing the map +* [mantyr/iterator](https://github.com/mantyr/iterator) also uses a channel for iterations, and its `Delete` operations are linear +* [samdolan/go-ordered-map](https://github.com/samdolan/go-ordered-map) adds unnecessary locking (users should add their own locking instead if they need it), its `Delete` and `Get` operations are linear, iterations trigger a linear memory allocation diff --git a/vendor/github.com/wk8/go-ordered-map/v2/json.go b/vendor/github.com/wk8/go-ordered-map/v2/json.go new file mode 100644 index 000000000..a545b536b --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/json.go @@ -0,0 +1,182 @@ +package orderedmap + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "reflect" + "unicode/utf8" + + "github.com/buger/jsonparser" + "github.com/mailru/easyjson/jwriter" +) + +var ( + _ json.Marshaler = &OrderedMap[int, any]{} + _ json.Unmarshaler = &OrderedMap[int, any]{} +) + +// MarshalJSON implements the json.Marshaler interface. +func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { //nolint:funlen + if om == nil || om.list == nil { + return []byte("null"), nil + } + + writer := jwriter.Writer{} + writer.RawByte('{') + + for pair, firstIteration := om.Oldest(), true; pair != nil; pair = pair.Next() { + if firstIteration { + firstIteration = false + } else { + writer.RawByte(',') + } + + switch key := any(pair.Key).(type) { + case string: + writer.String(key) + case encoding.TextMarshaler: + writer.RawByte('"') + writer.Raw(key.MarshalText()) + writer.RawByte('"') + case int: + writer.IntStr(key) + case int8: + writer.Int8Str(key) + case int16: + writer.Int16Str(key) + case int32: + writer.Int32Str(key) + case int64: + writer.Int64Str(key) + case uint: + writer.UintStr(key) + case uint8: + writer.Uint8Str(key) + case uint16: + writer.Uint16Str(key) + case uint32: + writer.Uint32Str(key) + case uint64: + writer.Uint64Str(key) + default: + + // this switch takes care of wrapper types around primitive types, such as + // type myType string + switch keyValue := reflect.ValueOf(key); keyValue.Type().Kind() { + case reflect.String: + writer.String(keyValue.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + writer.Int64Str(keyValue.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + writer.Uint64Str(keyValue.Uint()) + default: + return nil, fmt.Errorf("unsupported key type: %T", key) + } + } + + writer.RawByte(':') + // the error is checked at the end of the function + writer.Raw(json.Marshal(pair.Value)) //nolint:errchkjson + } + + writer.RawByte('}') + + return dumpWriter(&writer) +} + +func dumpWriter(writer *jwriter.Writer) ([]byte, error) { + if writer.Error != nil { + return nil, writer.Error + } + + var buf bytes.Buffer + buf.Grow(writer.Size()) + if _, err := writer.DumpTo(&buf); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error { + if om.list == nil { + om.initialize(0) + } + + return jsonparser.ObjectEach( + data, + func(keyData []byte, valueData []byte, dataType jsonparser.ValueType, offset int) error { + if dataType == jsonparser.String { + // jsonparser removes the enclosing quotes; we need to restore them to make a valid JSON + valueData = data[offset-len(valueData)-2 : offset] + } + + var key K + var value V + + switch typedKey := any(&key).(type) { + case *string: + s, err := decodeUTF8(keyData) + if err != nil { + return err + } + *typedKey = s + case encoding.TextUnmarshaler: + if err := typedKey.UnmarshalText(keyData); err != nil { + return err + } + case *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64: + if err := json.Unmarshal(keyData, typedKey); err != nil { + return err + } + default: + // this switch takes care of wrapper types around primitive types, such as + // type myType string + switch reflect.TypeOf(key).Kind() { + case reflect.String: + s, err := decodeUTF8(keyData) + if err != nil { + return err + } + + convertedKeyData := reflect.ValueOf(s).Convert(reflect.TypeOf(key)) + reflect.ValueOf(&key).Elem().Set(convertedKeyData) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if err := json.Unmarshal(keyData, &key); err != nil { + return err + } + default: + return fmt.Errorf("unsupported key type: %T", key) + } + } + + if err := json.Unmarshal(valueData, &value); err != nil { + return err + } + + om.Set(key, value) + return nil + }) +} + +func decodeUTF8(input []byte) (string, error) { + remaining, offset := input, 0 + runes := make([]rune, 0, len(remaining)) + + for len(remaining) > 0 { + r, size := utf8.DecodeRune(remaining) + if r == utf8.RuneError && size <= 1 { + return "", fmt.Errorf("not a valid UTF-8 string (at position %d): %s", offset, string(input)) + } + + runes = append(runes, r) + remaining = remaining[size:] + offset += size + } + + return string(runes), nil +} diff --git a/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go b/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go new file mode 100644 index 000000000..064714191 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/orderedmap.go @@ -0,0 +1,296 @@ +// Package orderedmap implements an ordered map, i.e. a map that also keeps track of +// the order in which keys were inserted. +// +// All operations are constant-time. +// +// Github repo: https://github.com/wk8/go-ordered-map +// +package orderedmap + +import ( + "fmt" + + list "github.com/bahlo/generic-list-go" +) + +type Pair[K comparable, V any] struct { + Key K + Value V + + element *list.Element[*Pair[K, V]] +} + +type OrderedMap[K comparable, V any] struct { + pairs map[K]*Pair[K, V] + list *list.List[*Pair[K, V]] +} + +type initConfig[K comparable, V any] struct { + capacity int + initialData []Pair[K, V] +} + +type InitOption[K comparable, V any] func(config *initConfig[K, V]) + +// WithCapacity allows giving a capacity hint for the map, akin to the standard make(map[K]V, capacity). +func WithCapacity[K comparable, V any](capacity int) InitOption[K, V] { + return func(c *initConfig[K, V]) { + c.capacity = capacity + } +} + +// WithInitialData allows passing in initial data for the map. +func WithInitialData[K comparable, V any](initialData ...Pair[K, V]) InitOption[K, V] { + return func(c *initConfig[K, V]) { + c.initialData = initialData + if c.capacity < len(initialData) { + c.capacity = len(initialData) + } + } +} + +// New creates a new OrderedMap. +// options can either be one or several InitOption[K, V], or a single integer, +// which is then interpreted as a capacity hint, à la make(map[K]V, capacity). +func New[K comparable, V any](options ...any) *OrderedMap[K, V] { //nolint:varnamelen + orderedMap := &OrderedMap[K, V]{} + + var config initConfig[K, V] + for _, untypedOption := range options { + switch option := untypedOption.(type) { + case int: + if len(options) != 1 { + invalidOption() + } + config.capacity = option + + case InitOption[K, V]: + option(&config) + + default: + invalidOption() + } + } + + orderedMap.initialize(config.capacity) + orderedMap.AddPairs(config.initialData...) + + return orderedMap +} + +const invalidOptionMessage = `when using orderedmap.New[K,V]() with options, either provide one or several InitOption[K, V]; or a single integer which is then interpreted as a capacity hint, à la make(map[K]V, capacity).` //nolint:lll + +func invalidOption() { panic(invalidOptionMessage) } + +func (om *OrderedMap[K, V]) initialize(capacity int) { + om.pairs = make(map[K]*Pair[K, V], capacity) + om.list = list.New[*Pair[K, V]]() +} + +// Get looks for the given key, and returns the value associated with it, +// or V's nil value if not found. The boolean it returns says whether the key is present in the map. +func (om *OrderedMap[K, V]) Get(key K) (val V, present bool) { + if pair, present := om.pairs[key]; present { + return pair.Value, true + } + + return +} + +// Load is an alias for Get, mostly to present an API similar to `sync.Map`'s. +func (om *OrderedMap[K, V]) Load(key K) (V, bool) { + return om.Get(key) +} + +// Value returns the value associated with the given key or the zero value. +func (om *OrderedMap[K, V]) Value(key K) (val V) { + if pair, present := om.pairs[key]; present { + val = pair.Value + } + return +} + +// GetPair looks for the given key, and returns the pair associated with it, +// or nil if not found. The Pair struct can then be used to iterate over the ordered map +// from that point, either forward or backward. +func (om *OrderedMap[K, V]) GetPair(key K) *Pair[K, V] { + return om.pairs[key] +} + +// Set sets the key-value pair, and returns what `Get` would have returned +// on that key prior to the call to `Set`. +func (om *OrderedMap[K, V]) Set(key K, value V) (val V, present bool) { + if pair, present := om.pairs[key]; present { + oldValue := pair.Value + pair.Value = value + return oldValue, true + } + + pair := &Pair[K, V]{ + Key: key, + Value: value, + } + pair.element = om.list.PushBack(pair) + om.pairs[key] = pair + + return +} + +// AddPairs allows setting multiple pairs at a time. It's equivalent to calling +// Set on each pair sequentially. +func (om *OrderedMap[K, V]) AddPairs(pairs ...Pair[K, V]) { + for _, pair := range pairs { + om.Set(pair.Key, pair.Value) + } +} + +// Store is an alias for Set, mostly to present an API similar to `sync.Map`'s. +func (om *OrderedMap[K, V]) Store(key K, value V) (V, bool) { + return om.Set(key, value) +} + +// Delete removes the key-value pair, and returns what `Get` would have returned +// on that key prior to the call to `Delete`. +func (om *OrderedMap[K, V]) Delete(key K) (val V, present bool) { + if pair, present := om.pairs[key]; present { + om.list.Remove(pair.element) + delete(om.pairs, key) + return pair.Value, true + } + return +} + +// Len returns the length of the ordered map. +func (om *OrderedMap[K, V]) Len() int { + if om == nil || om.pairs == nil { + return 0 + } + return len(om.pairs) +} + +// Oldest returns a pointer to the oldest pair. It's meant to be used to iterate on the ordered map's +// pairs from the oldest to the newest, e.g.: +// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } +func (om *OrderedMap[K, V]) Oldest() *Pair[K, V] { + if om == nil || om.list == nil { + return nil + } + return listElementToPair(om.list.Front()) +} + +// Newest returns a pointer to the newest pair. It's meant to be used to iterate on the ordered map's +// pairs from the newest to the oldest, e.g.: +// for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() { fmt.Printf("%v => %v\n", pair.Key, pair.Value) } +func (om *OrderedMap[K, V]) Newest() *Pair[K, V] { + if om == nil || om.list == nil { + return nil + } + return listElementToPair(om.list.Back()) +} + +// Next returns a pointer to the next pair. +func (p *Pair[K, V]) Next() *Pair[K, V] { + return listElementToPair(p.element.Next()) +} + +// Prev returns a pointer to the previous pair. +func (p *Pair[K, V]) Prev() *Pair[K, V] { + return listElementToPair(p.element.Prev()) +} + +func listElementToPair[K comparable, V any](element *list.Element[*Pair[K, V]]) *Pair[K, V] { + if element == nil { + return nil + } + return element.Value +} + +// KeyNotFoundError may be returned by functions in this package when they're called with keys that are not present +// in the map. +type KeyNotFoundError[K comparable] struct { + MissingKey K +} + +func (e *KeyNotFoundError[K]) Error() string { + return fmt.Sprintf("missing key: %v", e.MissingKey) +} + +// MoveAfter moves the value associated with key to its new position after the one associated with markKey. +// Returns an error iff key or markKey are not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveAfter(key, markKey K) error { + elements, err := om.getElements(key, markKey) + if err != nil { + return err + } + om.list.MoveAfter(elements[0], elements[1]) + return nil +} + +// MoveBefore moves the value associated with key to its new position before the one associated with markKey. +// Returns an error iff key or markKey are not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveBefore(key, markKey K) error { + elements, err := om.getElements(key, markKey) + if err != nil { + return err + } + om.list.MoveBefore(elements[0], elements[1]) + return nil +} + +func (om *OrderedMap[K, V]) getElements(keys ...K) ([]*list.Element[*Pair[K, V]], error) { + elements := make([]*list.Element[*Pair[K, V]], len(keys)) + for i, k := range keys { + pair, present := om.pairs[k] + if !present { + return nil, &KeyNotFoundError[K]{k} + } + elements[i] = pair.element + } + return elements, nil +} + +// MoveToBack moves the value associated with key to the back of the ordered map, +// i.e. makes it the newest pair in the map. +// Returns an error iff key is not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveToBack(key K) error { + _, err := om.GetAndMoveToBack(key) + return err +} + +// MoveToFront moves the value associated with key to the front of the ordered map, +// i.e. makes it the oldest pair in the map. +// Returns an error iff key is not present in the map. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) MoveToFront(key K) error { + _, err := om.GetAndMoveToFront(key) + return err +} + +// GetAndMoveToBack combines Get and MoveToBack in the same call. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) GetAndMoveToBack(key K) (val V, err error) { + if pair, present := om.pairs[key]; present { + val = pair.Value + om.list.MoveToBack(pair.element) + } else { + err = &KeyNotFoundError[K]{key} + } + + return +} + +// GetAndMoveToFront combines Get and MoveToFront in the same call. If an error is returned, +// it will be a KeyNotFoundError. +func (om *OrderedMap[K, V]) GetAndMoveToFront(key K) (val V, err error) { + if pair, present := om.pairs[key]; present { + val = pair.Value + om.list.MoveToFront(pair.element) + } else { + err = &KeyNotFoundError[K]{key} + } + + return +} diff --git a/vendor/github.com/wk8/go-ordered-map/v2/yaml.go b/vendor/github.com/wk8/go-ordered-map/v2/yaml.go new file mode 100644 index 000000000..602247128 --- /dev/null +++ b/vendor/github.com/wk8/go-ordered-map/v2/yaml.go @@ -0,0 +1,71 @@ +package orderedmap + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +var ( + _ yaml.Marshaler = &OrderedMap[int, any]{} + _ yaml.Unmarshaler = &OrderedMap[int, any]{} +) + +// MarshalYAML implements the yaml.Marshaler interface. +func (om *OrderedMap[K, V]) MarshalYAML() (interface{}, error) { + if om == nil { + return []byte("null"), nil + } + + node := yaml.Node{ + Kind: yaml.MappingNode, + } + + for pair := om.Oldest(); pair != nil; pair = pair.Next() { + key, value := pair.Key, pair.Value + + keyNode := &yaml.Node{} + + // serialize key to yaml, then deserialize it back into the node + // this is a hack to get the correct tag for the key + if err := keyNode.Encode(key); err != nil { + return nil, err + } + + valueNode := &yaml.Node{} + if err := valueNode.Encode(value); err != nil { + return nil, err + } + + node.Content = append(node.Content, keyNode, valueNode) + } + + return &node, nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (om *OrderedMap[K, V]) UnmarshalYAML(value *yaml.Node) error { + if value.Kind != yaml.MappingNode { + return fmt.Errorf("pipeline must contain YAML mapping, has %v", value.Kind) + } + + if om.list == nil { + om.initialize(0) + } + + for index := 0; index < len(value.Content); index += 2 { + var key K + var val V + + if err := value.Content[index].Decode(&key); err != nil { + return err + } + if err := value.Content[index+1].Decode(&val); err != nil { + return err + } + + om.Set(key, val) + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 20e53a31f..ee08e3148 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,6 +45,9 @@ github.com/antlr/antlr4/runtime/Go/antlr/v4 # github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 ## explicit; go 1.13 github.com/asaskevich/govalidator +# github.com/bahlo/generic-list-go v0.2.0 +## explicit; go 1.18 +github.com/bahlo/generic-list-go # github.com/beorn7/perks v1.0.1 ## explicit; go 1.11 github.com/beorn7/perks/quantile @@ -54,6 +57,9 @@ github.com/blang/semver # github.com/blang/semver/v4 v4.0.0 ## explicit; go 1.14 github.com/blang/semver/v4 +# github.com/buger/jsonparser v1.1.1 +## explicit; go 1.13 +github.com/buger/jsonparser # github.com/cenkalti/backoff/v4 v4.2.1 ## explicit; go 1.18 github.com/cenkalti/backoff/v4 @@ -311,6 +317,9 @@ github.com/inconshreveable/go-update/internal/osext # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap +# github.com/invopop/jsonschema v0.12.0 +## explicit; go 1.18 +github.com/invopop/jsonschema # github.com/jonboulle/clockwork v0.4.0 ## explicit; go 1.15 github.com/jonboulle/clockwork @@ -431,10 +440,6 @@ github.com/loft-sh/utils/pkg/command github.com/loft-sh/utils/pkg/downloader github.com/loft-sh/utils/pkg/downloader/commands github.com/loft-sh/utils/pkg/extract -# github.com/loft-sh/vcluster-values v0.0.0-20240207093538-4bbb24e9f699 -## explicit; go 1.21.5 -github.com/loft-sh/vcluster-values/helmvalues -github.com/loft-sh/vcluster-values/values # github.com/mailru/easyjson v0.7.7 ## explicit; go 1.12 github.com/mailru/easyjson/buffer @@ -604,6 +609,9 @@ github.com/ulikunitz/xz/lzma # github.com/vmware-labs/yaml-jsonpath v0.3.2 ## explicit; go 1.13 github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath +# github.com/wk8/go-ordered-map/v2 v2.1.8 +## explicit; go 1.18 +github.com/wk8/go-ordered-map/v2 # github.com/xlab/treeprint v1.2.0 ## explicit; go 1.13 github.com/xlab/treeprint