mirror of
https://github.com/rancher/k3k.git
synced 2026-04-10 12:46:59 +00:00
Compare commits
21 Commits
chart-1.1.
...
chart-1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efbd179a77 | ||
|
|
746fd3c36c | ||
|
|
efc28292ee | ||
|
|
0395befc54 | ||
|
|
d5f3c3e304 | ||
|
|
e21b1b6ba6 | ||
|
|
d7ab4320c7 | ||
|
|
96d812a3cb | ||
|
|
1808926d44 | ||
|
|
3ec41c3717 | ||
|
|
8add97934e | ||
|
|
ba819618e3 | ||
|
|
c4cd768789 | ||
|
|
183c5a0a5a | ||
|
|
20b42b0dcf | ||
|
|
ef2bb0339a | ||
|
|
93e8ab6d8f | ||
|
|
18889ba6b7 | ||
|
|
491e43057c | ||
|
|
8403214d58 | ||
|
|
624fee98d7 |
3
.github/renovate.json
vendored
3
.github/renovate.json
vendored
@@ -4,7 +4,8 @@
|
||||
"github>rancher/renovate-config#release"
|
||||
],
|
||||
"baseBranchPatterns": [
|
||||
"main"
|
||||
"main",
|
||||
"release/v1.0"
|
||||
],
|
||||
"prHourlyLimit": 2
|
||||
}
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
output: 'trivy-results-k3kcli.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab (k3kcli)
|
||||
uses: github/codeql-action/upload-sarif@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
|
||||
uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3
|
||||
with:
|
||||
sarif_file: trivy-results-k3kcli.sarif
|
||||
category: k3kcli
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
output: 'trivy-results-k3k.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab (k3k)
|
||||
uses: github/codeql-action/upload-sarif@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
|
||||
uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3
|
||||
with:
|
||||
sarif_file: trivy-results-k3k.sarif
|
||||
category: k3k
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
output: 'trivy-results-k3k-kubelet.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab (k3k-kubelet)
|
||||
uses: github/codeql-action/upload-sarif@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
|
||||
uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3
|
||||
with:
|
||||
sarif_file: trivy-results-k3k-kubelet.sarif
|
||||
category: k3k-kubelet
|
||||
|
||||
17
.github/workflows/chart.yml
vendored
17
.github/workflows/chart.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
HELM_VERSION: v4.1.3
|
||||
HELM_BIN_HASH_AMD64: 02ce9722d541238f81459938b84cf47df2fdf1187493b4bfb2346754d82a4700
|
||||
|
||||
jobs:
|
||||
chart-release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -19,11 +23,14 @@ jobs:
|
||||
run: |
|
||||
git config user.name "$GITHUB_ACTOR"
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Install helm
|
||||
run: |
|
||||
curl -sSfL -o helm.tar.gz https://get.helm.sh/helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz
|
||||
echo "${{ env.HELM_BIN_HASH_AMD64 }} helm.tar.gz" | sha256sum --check
|
||||
tar -xvzf helm.tar.gz --strip-components=1 -C /tmp/
|
||||
sudo mv /tmp/helm /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/helm
|
||||
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
|
||||
|
||||
4
.github/workflows/fossa.yml
vendored
4
.github/workflows/fossa.yml
vendored
@@ -20,13 +20,13 @@ jobs:
|
||||
# The FOSSA token is shared between all repos in Rancher's GH org. It can be
|
||||
# used directly and there is no need to request specific access to EIO.
|
||||
- name: Read FOSSA token
|
||||
uses: rancher-eio/read-vault-secrets@main
|
||||
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
|
||||
with:
|
||||
secrets: |
|
||||
secret/data/github/org/rancher/fossa/push token | FOSSA_API_KEY_PUSH_ONLY
|
||||
|
||||
- name: FOSSA scan
|
||||
uses: fossas/fossa-action@main
|
||||
uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0
|
||||
with:
|
||||
api-key: ${{ env.FOSSA_API_KEY_PUSH_ONLY }}
|
||||
# Only runs the scan and do not provide/returns any results back to the
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
run: git checkout ${{ inputs.commit }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
version: v0.30.1
|
||||
|
||||
- name: "Read secrets"
|
||||
uses: rancher-eio/read-vault-secrets@main
|
||||
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
|
||||
if: github.repository_owner == 'rancher'
|
||||
with:
|
||||
secrets: |
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
echo "DOCKER_PASSWORD=${{ github.token }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to container registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ env.DOCKER_USERNAME }}
|
||||
|
||||
2
.github/workflows/renovate-vault.yml
vendored
2
.github/workflows/renovate-vault.yml
vendored
@@ -51,7 +51,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: rancher/renovate-config/.github/workflows/renovate-vault.yml@release
|
||||
uses: rancher/renovate-config/.github/workflows/renovate-vault.yml@240174f0ae1994c3f6e94b8b062ea4aceed4a182 # release
|
||||
with:
|
||||
configMigration: ${{ inputs.configMigration || 'true' }}
|
||||
logLevel: ${{ inputs.logLevel || 'info' }}
|
||||
|
||||
124
.github/workflows/test-conformance-shared.yaml
vendored
124
.github/workflows/test-conformance-shared.yaml
vendored
@@ -4,20 +4,56 @@ on:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
k3k_version:
|
||||
description: 'K3k version to test (e.g. v1.0.2). Leave empty to build from source.'
|
||||
required: false
|
||||
type: string
|
||||
k8s_version:
|
||||
description: 'Kubernetes version to test'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- ""
|
||||
- "v1.34.6"
|
||||
- "v1.35.3"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
K8S_VERSIONS: "v1.34.6,v1.35.3"
|
||||
HELM_VERSION: v4.1.3
|
||||
HELM_BIN_HASH_AMD64: 02ce9722d541238f81459938b84cf47df2fdf1187493b4bfb2346754d82a4700
|
||||
K3D_VERSION: v5.8.3
|
||||
K3D_BIN_HASH_AMD64: dbaa79a76ace7f4ca230a1ff41dc7d8a5036a8ad0309e9c54f9bf3836dbe853e
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
k8s_versions: ${{ steps.set-matrix.outputs.k8s_versions }}
|
||||
steps:
|
||||
- id: set-matrix
|
||||
run: |
|
||||
if [[ -z "${{ inputs.k8s_version }}" ]]; then
|
||||
JSON_ARRAY=$(jq -nc '"${{ env.K8S_VERSIONS }}" | split(",")')
|
||||
echo "k8s_versions=${JSON_ARRAY}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "k8s_versions=[\"${{ inputs.k8s_version }}\"]" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
conformance:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
type:
|
||||
- parallel
|
||||
- serial
|
||||
k8s_version: ${{ fromJSON(needs.setup.outputs.k8s_versions) }}
|
||||
|
||||
env:
|
||||
KUBERNETES_VERSION: ${{ matrix.k8s_version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -26,22 +62,33 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
|
||||
|
||||
run: |
|
||||
curl -sSfL -o helm.tar.gz https://get.helm.sh/helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz
|
||||
echo "${{ env.HELM_BIN_HASH_AMD64 }} helm.tar.gz" | sha256sum --check
|
||||
tar -xvzf helm.tar.gz --strip-components=1 -C /tmp/
|
||||
sudo mv /tmp/helm /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/helm
|
||||
|
||||
- name: Install hydrophone
|
||||
run: go install sigs.k8s.io/hydrophone@latest
|
||||
run: go install sigs.k8s.io/hydrophone@3de3e886a2f6f09635d8b981c195490af1584d97 #v0.7.0
|
||||
|
||||
- name: Install k3d # taken from github.com/rancher/rancher/.github/workflows/integration-tests.yaml
|
||||
run: |
|
||||
curl -sSfL -o k3d "https://github.com/k3d-io/k3d/releases/download/${{ env.K3D_VERSION }}/k3d-linux-amd64"
|
||||
echo "${{ env.K3D_BIN_HASH_AMD64 }} k3d" | sha256sum --check
|
||||
sudo mv k3d /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/k3d
|
||||
|
||||
- name: Install k3d and kubectl
|
||||
run: |
|
||||
wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
|
||||
k3d version
|
||||
|
||||
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
||||
curl -LO "https://dl.k8s.io/release/${{ env.KUBERNETES_VERSION }}/bin/linux/amd64/kubectl"
|
||||
curl -LO "https://dl.k8s.io/release/${{ env.KUBERNETES_VERSION }}/bin/linux/amd64/kubectl.sha256"
|
||||
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
|
||||
|
||||
- name: Setup Kubernetes (k3d)
|
||||
env:
|
||||
@@ -53,13 +100,15 @@ jobs:
|
||||
k3d registry create ${REPO_NAME} --port ${REPO_PORT}
|
||||
|
||||
k3d cluster create k3k --servers 2 \
|
||||
--image rancher/k3s:${{ env.KUBERNETES_VERSION }}-k3s1 \
|
||||
-p "30000-30010:30000-30010@server:0" \
|
||||
--registry-use k3d-${REPO_NAME}:${REPO_PORT}
|
||||
|
||||
kubectl cluster-info
|
||||
kubectl get nodes
|
||||
|
||||
- name: Setup K3k
|
||||
- name: Setup K3k (from source)
|
||||
if: inputs.k3k_version == ''
|
||||
env:
|
||||
REPO: k3k-registry:12345
|
||||
run: |
|
||||
@@ -77,9 +126,29 @@ jobs:
|
||||
k3d image import ${REPO}/k3k-kubelet:${VERSION} -c k3k --verbose
|
||||
|
||||
make install
|
||||
|
||||
- name: Setup K3k (from release)
|
||||
if: inputs.k3k_version != ''
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
K3K_VERSION="${{ inputs.k3k_version }}"
|
||||
CHART_VERSION="${K3K_VERSION#v}"
|
||||
|
||||
helm repo add k3k https://rancher.github.io/k3k
|
||||
helm repo update
|
||||
helm install --namespace k3k-system --create-namespace --version "${CHART_VERSION}" k3k k3k/k3k
|
||||
|
||||
echo "Wait for K3k controller to be available"
|
||||
kubectl wait -n k3k-system pod --for condition=Ready -l "app.kubernetes.io/name=k3k" --timeout=5m
|
||||
wget -qO k3kcli "https://github.com/rancher/k3k/releases/download/${{ inputs.k3k_version }}/k3kcli-linux-amd64"
|
||||
sudo mv k3kcli /usr/local/bin/k3kcli
|
||||
sudo chmod +x /usr/local/bin/k3kcli
|
||||
|
||||
- name: Wait for K3k controller
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
echo "Wait for K3k controller deployment to be available"
|
||||
kubectl wait -n k3k-system deployment -l "app.kubernetes.io/name=k3k" --for=condition=Available --timeout=5m
|
||||
|
||||
- name: Check k3kcli
|
||||
run: k3kcli -v
|
||||
@@ -114,33 +183,30 @@ jobs:
|
||||
kubectl get nodes
|
||||
kubectl get pods -A
|
||||
|
||||
- name: Run conformance tests (parallel)
|
||||
if: matrix.type == 'parallel'
|
||||
- name: Run conformance tests
|
||||
run: |
|
||||
# Run conformance tests in parallel mode (skipping serial)
|
||||
hydrophone --conformance --parallel 4 --skip='\[Serial\]' \
|
||||
hydrophone --conformance --parallel 4 \
|
||||
--kubeconfig ${{ github.workspace }}/k3k-mycluster-mycluster-kubeconfig.yaml \
|
||||
--output-dir /tmp
|
||||
|
||||
- name: Run conformance tests (serial)
|
||||
if: matrix.type == 'serial'
|
||||
run: |
|
||||
# Run serial conformance tests
|
||||
hydrophone --focus='\[Serial\].*\[Conformance\]' \
|
||||
--kubeconfig ${{ github.workspace }}/k3k-mycluster-mycluster-kubeconfig.yaml \
|
||||
--output-dir /tmp
|
||||
|
||||
- name: Archive conformance logs
|
||||
- name: Archive logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: conformance-${{ matrix.type }}-logs
|
||||
name: conformance-${{ matrix.k8s_version }}-logs
|
||||
path: /tmp/e2e.log
|
||||
|
||||
- name: Archive results
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: conformance-${{ matrix.k8s_version }}-results
|
||||
path: /tmp/junit_01.xml
|
||||
|
||||
- name: Job Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo '## 📊 Conformance Tests Results (${{ matrix.type }})' >> $GITHUB_STEP_SUMMARY
|
||||
echo '## 📊 Conformance Tests Results (${{ matrix.k8s_version }})' >> $GITHUB_STEP_SUMMARY
|
||||
echo '| Passed | Failed | Pending | Skipped |' >> $GITHUB_STEP_SUMMARY
|
||||
echo '|---|---|---|---|' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
113
.github/workflows/test-conformance-virtual.yaml
vendored
113
.github/workflows/test-conformance-virtual.yaml
vendored
@@ -4,20 +4,54 @@ on:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
k3k_version:
|
||||
description: 'K3k version to test (e.g. v1.0.2). Leave empty to build from source.'
|
||||
required: false
|
||||
type: string
|
||||
k8s_version:
|
||||
description: 'Kubernetes version to test'
|
||||
required: false
|
||||
type: choice
|
||||
options:
|
||||
- ""
|
||||
- "v1.34.6"
|
||||
- "v1.35.3"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
K8S_VERSIONS: "v1.34.6,v1.35.3"
|
||||
HELM_VERSION: v4.1.3
|
||||
HELM_BIN_HASH_AMD64: 02ce9722d541238f81459938b84cf47df2fdf1187493b4bfb2346754d82a4700
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
k8s_versions: ${{ steps.set-matrix.outputs.k8s_versions }}
|
||||
steps:
|
||||
- id: set-matrix
|
||||
run: |
|
||||
if [[ -z "${{ inputs.k8s_version }}" ]]; then
|
||||
JSON_ARRAY=$(jq -nc '"${{ env.K8S_VERSIONS }}" | split(",")')
|
||||
echo "k8s_versions=${JSON_ARRAY}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "k8s_versions=[\"${{ inputs.k8s_version }}\"]" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
conformance:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
type:
|
||||
- parallel
|
||||
- serial
|
||||
k8s_version: ${{ fromJSON(needs.setup.outputs.k8s_versions) }}
|
||||
|
||||
env:
|
||||
KUBERNETES_VERSION: ${{ matrix.k8s_version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -26,27 +60,33 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install helm
|
||||
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
|
||||
|
||||
run: |
|
||||
curl -sSfL -o helm.tar.gz https://get.helm.sh/helm-${{ env.HELM_VERSION }}-linux-amd64.tar.gz
|
||||
echo "${{ env.HELM_BIN_HASH_AMD64 }} helm.tar.gz" | sha256sum --check
|
||||
tar -xvzf helm.tar.gz --strip-components=1 -C /tmp/
|
||||
sudo mv /tmp/helm /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/helm
|
||||
|
||||
- name: Install hydrophone
|
||||
run: go install sigs.k8s.io/hydrophone@latest
|
||||
run: go install sigs.k8s.io/hydrophone@3de3e886a2f6f09635d8b981c195490af1584d97 #v0.7.0
|
||||
|
||||
- name: Install k3s
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
K3S_HOST_VERSION: v1.35.2+k3s1
|
||||
K3S_HOST_VERSION: ${{ env.KUBERNETES_VERSION }}+k3s1
|
||||
run: |
|
||||
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=${K3S_HOST_VERSION} INSTALL_K3S_EXEC="--write-kubeconfig-mode=777" sh -s -
|
||||
|
||||
kubectl cluster-info
|
||||
kubectl get nodes
|
||||
|
||||
- name: Build, package and setup K3k
|
||||
- name: Setup K3k (from source)
|
||||
if: inputs.k3k_version == ''
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
@@ -60,9 +100,29 @@ jobs:
|
||||
|
||||
# add k3kcli to $PATH
|
||||
echo "${{ github.workspace }}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Setup K3k (from release)
|
||||
if: inputs.k3k_version != ''
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
K3K_VERSION="${{ inputs.k3k_version }}"
|
||||
CHART_VERSION="${K3K_VERSION#v}"
|
||||
|
||||
helm repo add k3k https://rancher.github.io/k3k
|
||||
helm repo update
|
||||
helm install --namespace k3k-system --create-namespace --version "${CHART_VERSION}" k3k k3k/k3k
|
||||
|
||||
echo "Wait for K3k controller to be available"
|
||||
kubectl wait -n k3k-system pod --for condition=Ready -l "app.kubernetes.io/name=k3k" --timeout=5m
|
||||
wget -qO k3kcli "https://github.com/rancher/k3k/releases/download/${{ inputs.k3k_version }}/k3kcli-linux-amd64"
|
||||
sudo mv k3kcli /usr/local/bin/k3kcli
|
||||
sudo chmod +x /usr/local/bin/k3kcli
|
||||
|
||||
- name: Wait for K3k controller
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
echo "Wait for K3k controller deployment to be available"
|
||||
kubectl wait -n k3k-system deployment -l "app.kubernetes.io/name=k3k" --for=condition=Available --timeout=5m
|
||||
|
||||
- name: Check k3kcli
|
||||
run: k3kcli -v
|
||||
@@ -79,23 +139,13 @@ jobs:
|
||||
kubectl get nodes
|
||||
kubectl get pods -A
|
||||
|
||||
- name: Run conformance tests (parallel)
|
||||
if: matrix.type == 'parallel'
|
||||
- name: Run conformance tests
|
||||
run: |
|
||||
# Run conformance tests in parallel mode (skipping serial)
|
||||
hydrophone --conformance --parallel 4 --skip='\[Serial\]' \
|
||||
hydrophone --conformance --parallel 4 \
|
||||
--kubeconfig ${{ github.workspace }}/k3k-mycluster-mycluster-kubeconfig.yaml \
|
||||
--output-dir /tmp
|
||||
|
||||
- name: Run conformance tests (serial)
|
||||
if: matrix.type == 'serial'
|
||||
run: |
|
||||
# Run serial conformance tests
|
||||
hydrophone --focus='\[Serial\].*\[Conformance\]' \
|
||||
--kubeconfig ${{ github.workspace }}/k3k-mycluster-mycluster-kubeconfig.yaml \
|
||||
--output-dir /tmp
|
||||
|
||||
- name: Export logs
|
||||
- name: Collect logs
|
||||
if: always()
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
@@ -107,27 +157,34 @@ jobs:
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: k3s-${{ matrix.type }}-logs
|
||||
name: k3s-${{ matrix.k8s_version }}-logs
|
||||
path: /tmp/k3s.log
|
||||
|
||||
- name: Archive K3k logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: k3k-${{ matrix.type }}-logs
|
||||
name: k3k-${{ matrix.k8s_version }}-logs
|
||||
path: /tmp/k3k.log
|
||||
|
||||
- name: Archive conformance logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: conformance-${{ matrix.type }}-logs
|
||||
name: conformance-${{ matrix.k8s_version }}-logs
|
||||
path: /tmp/e2e.log
|
||||
|
||||
- name: Archive results
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: conformance-${{ matrix.k8s_version }}-results
|
||||
path: /tmp/junit_01.xml
|
||||
|
||||
- name: Job Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo '## 📊 Conformance Tests Results (${{ matrix.type }})' >> $GITHUB_STEP_SUMMARY
|
||||
echo '## 📊 Conformance Tests Results (${{ matrix.k8s_version }})' >> $GITHUB_STEP_SUMMARY
|
||||
echo '| Passed | Failed | Pending | Skipped |' >> $GITHUB_STEP_SUMMARY
|
||||
echo '|---|---|---|---|' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
47
.github/workflows/test-e2e.yaml
vendored
47
.github/workflows/test-e2e.yaml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
KUBERNETES_VERSION: v1.35.3
|
||||
|
||||
jobs:
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -21,12 +24,12 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install Ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo@5d1d628ac86668c8f944c8c491c3d1ab86b3bed4 #v2.28.1
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -36,11 +39,11 @@ jobs:
|
||||
echo "GOCOVERDIR=${{ github.workspace }}/covdata" >> $GITHUB_ENV
|
||||
echo "REPO=ttl.sh/$(uuidgen)" >> $GITHUB_ENV
|
||||
echo "VERSION=1h" >> $GITHUB_ENV
|
||||
echo "K3S_HOST_VERSION=v1.32.1+k3s1 >> $GITHUB_ENV"
|
||||
echo "K3S_HOST_VERSION=${{ env.KUBERNETES_VERSION }}+k3s1" >> $GITHUB_ENV
|
||||
|
||||
- name: Install k3s
|
||||
run: |
|
||||
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=${{ env.K3S_HOST_VERSION }} INSTALL_K3S_EXEC="--write-kubeconfig-mode=777" sh -s -
|
||||
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=${K3S_HOST_VERSION} INSTALL_K3S_EXEC="--write-kubeconfig-mode=777" sh -s -
|
||||
|
||||
- name: Build and package and push dev images
|
||||
env:
|
||||
@@ -64,19 +67,27 @@ jobs:
|
||||
run: go tool covdata textfmt -i=${GOCOVERDIR} -o ${GOCOVERDIR}/cover.out
|
||||
|
||||
- name: Upload coverage reports to Codecov (controller)
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${GOCOVERDIR}/cover.out
|
||||
flags: controller
|
||||
|
||||
- name: Upload coverage reports to Codecov (e2e)
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./cover.out
|
||||
flags: e2e
|
||||
|
||||
- name: Export logs
|
||||
if: always()
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
journalctl -u k3s -o cat --no-pager > /tmp/k3s.log
|
||||
kubectl logs -n k3k-system -l "app.kubernetes.io/name=k3k" --tail=-1 > /tmp/k3k.log
|
||||
|
||||
- name: Archive k3s logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
@@ -100,12 +111,12 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install Ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo@5d1d628ac86668c8f944c8c491c3d1ab86b3bed4 #v2.28.1
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -115,11 +126,11 @@ jobs:
|
||||
echo "GOCOVERDIR=${{ github.workspace }}/covdata" >> $GITHUB_ENV
|
||||
echo "REPO=ttl.sh/$(uuidgen)" >> $GITHUB_ENV
|
||||
echo "VERSION=1h" >> $GITHUB_ENV
|
||||
echo "K3S_HOST_VERSION=v1.32.1+k3s1 >> $GITHUB_ENV"
|
||||
echo "K3S_HOST_VERSION=${{ env.KUBERNETES_VERSION }}+k3s1" >> $GITHUB_ENV
|
||||
|
||||
- name: Install k3s
|
||||
run: |
|
||||
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=${{ env.K3S_HOST_VERSION }} INSTALL_K3S_EXEC="--write-kubeconfig-mode=777" sh -s -
|
||||
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=${K3S_HOST_VERSION} INSTALL_K3S_EXEC="--write-kubeconfig-mode=777" sh -s -
|
||||
|
||||
- name: Build and package and push dev images
|
||||
env:
|
||||
@@ -143,29 +154,37 @@ jobs:
|
||||
run: go tool covdata textfmt -i=${GOCOVERDIR} -o ${GOCOVERDIR}/cover.out
|
||||
|
||||
- name: Upload coverage reports to Codecov (controller)
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${GOCOVERDIR}/cover.out
|
||||
flags: controller
|
||||
|
||||
- name: Upload coverage reports to Codecov (e2e)
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./cover.out
|
||||
flags: e2e
|
||||
|
||||
- name: Export logs
|
||||
if: always()
|
||||
env:
|
||||
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
|
||||
run: |
|
||||
journalctl -u k3s -o cat --no-pager > /tmp/k3s.log
|
||||
kubectl logs -n k3k-system -l "app.kubernetes.io/name=k3k" --tail=-1 > /tmp/k3k.log
|
||||
|
||||
- name: Archive k3s logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-k3s-logs
|
||||
name: e2e-slow-k3s-logs
|
||||
path: /tmp/k3s.log
|
||||
|
||||
- name: Archive k3k logs
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
if: always()
|
||||
with:
|
||||
name: e2e-k3k-logs
|
||||
name: e2e-slow-k3k-logs
|
||||
path: /tmp/k3k.log
|
||||
15
.github/workflows/test.yaml
vendored
15
.github/workflows/test.yaml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
KUBERNETES_VERSION: v1.35.3
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -18,7 +21,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
@@ -26,7 +29,7 @@ jobs:
|
||||
run: make test-unit
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./cover.out
|
||||
@@ -42,12 +45,12 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install Ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo
|
||||
run: go install github.com/onsi/ginkgo/v2/ginkgo@5d1d628ac86668c8f944c8c491c3d1ab86b3bed4 #v2.28.1
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -55,7 +58,7 @@ jobs:
|
||||
|
||||
echo "COVERAGE=true" >> $GITHUB_ENV
|
||||
echo "GOCOVERDIR=${{ github.workspace }}/covdata" >> $GITHUB_ENV
|
||||
echo "K3S_HOST_VERSION=v1.32.1+k3s1 >> $GITHUB_ENV"
|
||||
echo "K3S_HOST_VERSION=${{ env.KUBERNETES_VERSION }}+k3s1" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and package
|
||||
run: |
|
||||
@@ -78,7 +81,7 @@ jobs:
|
||||
run: go tool covdata textfmt -i=${{ github.workspace }}/covdata -o ${{ github.workspace }}/covdata/cover.out
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${{ github.workspace }}/covdata/cover.out
|
||||
|
||||
4
.github/workflows/validate.yml
vendored
4
.github/workflows/validate.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Run linters
|
||||
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
||||
with:
|
||||
version: v2.11.3
|
||||
version: v2.11.4
|
||||
args: -v
|
||||
only-new-issues: true
|
||||
skip-cache: false
|
||||
|
||||
28
Makefile
28
Makefile
@@ -5,7 +5,7 @@ VERSION ?= $(shell git describe --tags --always --dirty --match="v[0-9]*")
|
||||
|
||||
## Dependencies
|
||||
|
||||
GOLANGCI_LINT_VERSION := v2.11.3
|
||||
GOLANGCI_LINT_VERSION := v2.11.4
|
||||
GINKGO_VERSION ?= v2.28.1
|
||||
GINKGO_FLAGS ?= -v -r --coverprofile=cover.out --coverpkg=./...
|
||||
ENVTEST_VERSION ?= v0.0.0-20250505003155-b6c5897febe5
|
||||
@@ -60,24 +60,32 @@ test: ## Run all the tests
|
||||
$(GINKGO) $(GINKGO_FLAGS) --label-filter=$(label-filter)
|
||||
|
||||
.PHONY: test-unit
|
||||
test-unit: ## Run the unit tests (skips the e2e)
|
||||
test-unit: ## Run the unit tests (skips the e2e and integration tests)
|
||||
$(GINKGO) $(GINKGO_FLAGS) --skip-file=tests/*
|
||||
|
||||
.PHONY: test-controller
|
||||
test-controller: ## Run the controller tests (pkg/controller)
|
||||
$(GINKGO) $(GINKGO_FLAGS) pkg/controller
|
||||
.PHONY: test-kubelet
|
||||
test-kubelet: ## Run the k3k-kubelet controller tests (tests/integration/k3k-kubelet)
|
||||
$(GINKGO) $(GINKGO_FLAGS) tests/integration/k3k-kubelet
|
||||
|
||||
.PHONY: test-kubelet-controller
|
||||
test-kubelet-controller: ## Run the controller tests (pkg/controller)
|
||||
$(GINKGO) $(GINKGO_FLAGS) k3k-kubelet/controller
|
||||
.PHONY: test-policy
|
||||
test-policy: ## Run the policy controller tests (tests/integration/policy)
|
||||
$(GINKGO) $(GINKGO_FLAGS) tests/integration/policy
|
||||
|
||||
.PHONY: test-cluster
|
||||
test-cluster: ## Run the cluster controller tests (tests/integration/cluster)
|
||||
$(GINKGO) $(GINKGO_FLAGS) tests/integration/cluster
|
||||
|
||||
.PHONY: test-integration
|
||||
test-integration: ## Run the controller tests that use envtest (tests/integration)
|
||||
$(GINKGO) $(GINKGO_FLAGS) tests/integration
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: ## Run the e2e tests
|
||||
$(GINKGO) $(GINKGO_FLAGS) --label-filter="$(E2E_LABEL_FILTER)" tests
|
||||
$(GINKGO) $(GINKGO_FLAGS) --flake-attempts=3 --label-filter="$(E2E_LABEL_FILTER)" tests/e2e
|
||||
|
||||
.PHONY: test-cli
|
||||
test-cli: ## Run the cli tests
|
||||
$(GINKGO) $(GINKGO_FLAGS) --label-filter=cli --flake-attempts=3 tests
|
||||
$(GINKGO) $(GINKGO_FLAGS) --flake-attempts=3 tests/cli
|
||||
|
||||
.PHONY: generate
|
||||
generate: ## Generate the CRDs specs
|
||||
|
||||
@@ -2,5 +2,5 @@ apiVersion: v2
|
||||
name: k3k
|
||||
description: A Helm chart for K3K
|
||||
type: application
|
||||
version: 1.1.0-rc2
|
||||
appVersion: v1.1.0-rc2
|
||||
version: 1.1.0-rc3
|
||||
appVersion: v1.1.0-rc3
|
||||
|
||||
@@ -1338,6 +1338,11 @@ spec:
|
||||
PriorityClass specifies the priorityClassName for server/agent pods.
|
||||
In "shared" mode, this also applies to workloads.
|
||||
type: string
|
||||
runtimeClassName:
|
||||
description: |-
|
||||
RuntimeClassName specifies alternative runtime class for the
|
||||
agent and server pods of the cluster in virtual or shared mode.
|
||||
type: string
|
||||
secretMounts:
|
||||
description: |-
|
||||
SecretMounts specifies a list of secrets to mount into server and agent pods.
|
||||
@@ -1426,6 +1431,194 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
securityContext:
|
||||
description: |-
|
||||
SecurityContext specifies custom SecurityContext to be added
|
||||
to the agent and server pods of the cluster in virtual or shared mode.
|
||||
This option will override the SecurityContext set by default for virtual mode.
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
description: |-
|
||||
AllowPrivilegeEscalation controls whether a process can gain more
|
||||
privileges than its parent process. This bool directly controls if
|
||||
the no_new_privs flag will be set on the container process.
|
||||
AllowPrivilegeEscalation is true always when the container is:
|
||||
1) run as Privileged
|
||||
2) has CAP_SYS_ADMIN
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
appArmorProfile:
|
||||
description: |-
|
||||
appArmorProfile is the AppArmor options to use by this container. If set, this profile
|
||||
overrides the pod's appArmorProfile.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile loaded on the node that should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must match the loaded name of the profile.
|
||||
Must be set if and only if type is "Localhost".
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of AppArmor profile will be applied.
|
||||
Valid options are:
|
||||
Localhost - a profile pre-loaded on the node.
|
||||
RuntimeDefault - the container runtime's default profile.
|
||||
Unconfined - no AppArmor enforcement.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
capabilities:
|
||||
description: |-
|
||||
The capabilities to add/drop when running containers.
|
||||
Defaults to the default set of capabilities granted by the container runtime.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
add:
|
||||
description: Added capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
drop:
|
||||
description: Removed capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
privileged:
|
||||
description: |-
|
||||
Run container in privileged mode.
|
||||
Processes in privileged containers are essentially equivalent to root on the host.
|
||||
Defaults to false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
procMount:
|
||||
description: |-
|
||||
procMount denotes the type of proc mount to use for the containers.
|
||||
The default value is Default which uses the container runtime defaults for
|
||||
readonly paths and masked paths.
|
||||
This requires the ProcMountType feature flag to be enabled.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
readOnlyRootFilesystem:
|
||||
description: |-
|
||||
Whether this container has a read-only root filesystem.
|
||||
Default is false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
runAsGroup:
|
||||
description: |-
|
||||
The GID to run the entrypoint of the container process.
|
||||
Uses runtime default if unset.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
runAsNonRoot:
|
||||
description: |-
|
||||
Indicates that the container must run as a non-root user.
|
||||
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||
does not run as UID 0 (root) and fail to start the container if it does.
|
||||
If unset or false, no such validation will be performed.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: |-
|
||||
The UID to run the entrypoint of the container process.
|
||||
Defaults to user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
seLinuxOptions:
|
||||
description: |-
|
||||
The SELinux context to be applied to the container.
|
||||
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
container. May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
type: object
|
||||
seccompProfile:
|
||||
description: |-
|
||||
The seccomp options to use by this container. If seccomp options are
|
||||
provided at both the pod & container level, the container options
|
||||
override the pod options.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of seccomp profile will be applied.
|
||||
Valid options are:
|
||||
|
||||
Localhost - a profile defined in a file on the node should be used.
|
||||
RuntimeDefault - the container runtime default profile should be used.
|
||||
Unconfined - no profile should be applied.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
windowsOptions:
|
||||
description: |-
|
||||
The Windows specific settings applied to all containers.
|
||||
If unspecified, the options from the PodSecurityContext will be used.
|
||||
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is linux.
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: |-
|
||||
GMSACredentialSpec is where the GMSA admission webhook
|
||||
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: |-
|
||||
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||
All of a Pod's containers must have the same effective HostProcess value
|
||||
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: |-
|
||||
The UserName in Windows to run the entrypoint of the container process.
|
||||
Defaults to the user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
serverAffinity:
|
||||
description: |-
|
||||
ServerAffinity specifies the affinity rules for server pods.
|
||||
@@ -2511,6 +2704,12 @@ spec:
|
||||
enabled: false
|
||||
description: Ingresses resources sync configuration.
|
||||
properties:
|
||||
disableTLSSecretTranslation:
|
||||
default: false
|
||||
description: |-
|
||||
DisableTLSSecretTranslation is an on/off switch for translating TLS secrets
|
||||
from virtual cluster to host cluster
|
||||
type: boolean
|
||||
enabled:
|
||||
default: false
|
||||
description: Enabled is an on/off switch for syncing resources.
|
||||
@@ -3628,6 +3827,198 @@ spec:
|
||||
priorityClass:
|
||||
description: priorityClass is the priority class enforced by the active VirtualClusterPolicy.
|
||||
type: string
|
||||
runtimeClassName:
|
||||
description: |-
|
||||
SecurityContext specifies custom SecurityContext to be added
|
||||
to the agent and server pods of the cluster in virtual or shared mode.
|
||||
type: string
|
||||
securityContext:
|
||||
description: |-
|
||||
RuntimeClassName specifies alternative runtime class for the
|
||||
agent and server pods of the cluster in virtual or shared mode.
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
description: |-
|
||||
AllowPrivilegeEscalation controls whether a process can gain more
|
||||
privileges than its parent process. This bool directly controls if
|
||||
the no_new_privs flag will be set on the container process.
|
||||
AllowPrivilegeEscalation is true always when the container is:
|
||||
1) run as Privileged
|
||||
2) has CAP_SYS_ADMIN
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
appArmorProfile:
|
||||
description: |-
|
||||
appArmorProfile is the AppArmor options to use by this container. If set, this profile
|
||||
overrides the pod's appArmorProfile.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile loaded on the node that should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must match the loaded name of the profile.
|
||||
Must be set if and only if type is "Localhost".
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of AppArmor profile will be applied.
|
||||
Valid options are:
|
||||
Localhost - a profile pre-loaded on the node.
|
||||
RuntimeDefault - the container runtime's default profile.
|
||||
Unconfined - no AppArmor enforcement.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
capabilities:
|
||||
description: |-
|
||||
The capabilities to add/drop when running containers.
|
||||
Defaults to the default set of capabilities granted by the container runtime.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
add:
|
||||
description: Added capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
drop:
|
||||
description: Removed capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
privileged:
|
||||
description: |-
|
||||
Run container in privileged mode.
|
||||
Processes in privileged containers are essentially equivalent to root on the host.
|
||||
Defaults to false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
procMount:
|
||||
description: |-
|
||||
procMount denotes the type of proc mount to use for the containers.
|
||||
The default value is Default which uses the container runtime defaults for
|
||||
readonly paths and masked paths.
|
||||
This requires the ProcMountType feature flag to be enabled.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
readOnlyRootFilesystem:
|
||||
description: |-
|
||||
Whether this container has a read-only root filesystem.
|
||||
Default is false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
runAsGroup:
|
||||
description: |-
|
||||
The GID to run the entrypoint of the container process.
|
||||
Uses runtime default if unset.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
runAsNonRoot:
|
||||
description: |-
|
||||
Indicates that the container must run as a non-root user.
|
||||
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||
does not run as UID 0 (root) and fail to start the container if it does.
|
||||
If unset or false, no such validation will be performed.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: |-
|
||||
The UID to run the entrypoint of the container process.
|
||||
Defaults to user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
seLinuxOptions:
|
||||
description: |-
|
||||
The SELinux context to be applied to the container.
|
||||
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
container. May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
type: object
|
||||
seccompProfile:
|
||||
description: |-
|
||||
The seccomp options to use by this container. If seccomp options are
|
||||
provided at both the pod & container level, the container options
|
||||
override the pod options.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of seccomp profile will be applied.
|
||||
Valid options are:
|
||||
|
||||
Localhost - a profile defined in a file on the node should be used.
|
||||
RuntimeDefault - the container runtime default profile should be used.
|
||||
Unconfined - no profile should be applied.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
windowsOptions:
|
||||
description: |-
|
||||
The Windows specific settings applied to all containers.
|
||||
If unspecified, the options from the PodSecurityContext will be used.
|
||||
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is linux.
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: |-
|
||||
GMSACredentialSpec is where the GMSA admission webhook
|
||||
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: |-
|
||||
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||
All of a Pod's containers must have the same effective HostProcess value
|
||||
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: |-
|
||||
The UserName in Windows to run the entrypoint of the container process.
|
||||
Defaults to the user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
serverAffinity:
|
||||
description: |-
|
||||
serverAffinity is the affinity rules for server pods enforced by the active VirtualClusterPolicy.
|
||||
@@ -4530,6 +4921,12 @@ spec:
|
||||
enabled: false
|
||||
description: Ingresses resources sync configuration.
|
||||
properties:
|
||||
disableTLSSecretTranslation:
|
||||
default: false
|
||||
description: |-
|
||||
DisableTLSSecretTranslation is an on/off switch for translating TLS secrets
|
||||
from virtual cluster to host cluster
|
||||
type: boolean
|
||||
enabled:
|
||||
default: false
|
||||
description: Enabled is an on/off switch for syncing resources.
|
||||
@@ -4652,9 +5049,6 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
webhookPort:
|
||||
description: WebhookPort specefies the port used by webhook in k3k-kubelet in shared mode.
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -1956,6 +1956,198 @@ spec:
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
runtimeClassName:
|
||||
description: |-
|
||||
SecurityContext specifies custom SecurityContext to be added
|
||||
to the agent and server pods of the cluster in virtual or shared mode.
|
||||
type: string
|
||||
securityContext:
|
||||
description: |-
|
||||
RuntimeClassName specifies alternative runtime class for the
|
||||
agent and server pods of the cluster in virtual or shared mode.
|
||||
properties:
|
||||
allowPrivilegeEscalation:
|
||||
description: |-
|
||||
AllowPrivilegeEscalation controls whether a process can gain more
|
||||
privileges than its parent process. This bool directly controls if
|
||||
the no_new_privs flag will be set on the container process.
|
||||
AllowPrivilegeEscalation is true always when the container is:
|
||||
1) run as Privileged
|
||||
2) has CAP_SYS_ADMIN
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
appArmorProfile:
|
||||
description: |-
|
||||
appArmorProfile is the AppArmor options to use by this container. If set, this profile
|
||||
overrides the pod's appArmorProfile.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile loaded on the node that should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must match the loaded name of the profile.
|
||||
Must be set if and only if type is "Localhost".
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of AppArmor profile will be applied.
|
||||
Valid options are:
|
||||
Localhost - a profile pre-loaded on the node.
|
||||
RuntimeDefault - the container runtime's default profile.
|
||||
Unconfined - no AppArmor enforcement.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
capabilities:
|
||||
description: |-
|
||||
The capabilities to add/drop when running containers.
|
||||
Defaults to the default set of capabilities granted by the container runtime.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
add:
|
||||
description: Added capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
drop:
|
||||
description: Removed capabilities
|
||||
items:
|
||||
description: Capability represent POSIX capabilities type
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
privileged:
|
||||
description: |-
|
||||
Run container in privileged mode.
|
||||
Processes in privileged containers are essentially equivalent to root on the host.
|
||||
Defaults to false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
procMount:
|
||||
description: |-
|
||||
procMount denotes the type of proc mount to use for the containers.
|
||||
The default value is Default which uses the container runtime defaults for
|
||||
readonly paths and masked paths.
|
||||
This requires the ProcMountType feature flag to be enabled.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: string
|
||||
readOnlyRootFilesystem:
|
||||
description: |-
|
||||
Whether this container has a read-only root filesystem.
|
||||
Default is false.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
type: boolean
|
||||
runAsGroup:
|
||||
description: |-
|
||||
The GID to run the entrypoint of the container process.
|
||||
Uses runtime default if unset.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
runAsNonRoot:
|
||||
description: |-
|
||||
Indicates that the container must run as a non-root user.
|
||||
If true, the Kubelet will validate the image at runtime to ensure that it
|
||||
does not run as UID 0 (root) and fail to start the container if it does.
|
||||
If unset or false, no such validation will be performed.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: boolean
|
||||
runAsUser:
|
||||
description: |-
|
||||
The UID to run the entrypoint of the container process.
|
||||
Defaults to user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
format: int64
|
||||
type: integer
|
||||
seLinuxOptions:
|
||||
description: |-
|
||||
The SELinux context to be applied to the container.
|
||||
If unspecified, the container runtime will allocate a random SELinux context for each
|
||||
container. May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
level:
|
||||
description: Level is SELinux level label that applies to the container.
|
||||
type: string
|
||||
role:
|
||||
description: Role is a SELinux role label that applies to the container.
|
||||
type: string
|
||||
type:
|
||||
description: Type is a SELinux type label that applies to the container.
|
||||
type: string
|
||||
user:
|
||||
description: User is a SELinux user label that applies to the container.
|
||||
type: string
|
||||
type: object
|
||||
seccompProfile:
|
||||
description: |-
|
||||
The seccomp options to use by this container. If seccomp options are
|
||||
provided at both the pod & container level, the container options
|
||||
override the pod options.
|
||||
Note that this field cannot be set when spec.os.name is windows.
|
||||
properties:
|
||||
localhostProfile:
|
||||
description: |-
|
||||
localhostProfile indicates a profile defined in a file on the node should be used.
|
||||
The profile must be preconfigured on the node to work.
|
||||
Must be a descending path, relative to the kubelet's configured seccomp profile location.
|
||||
Must be set if type is "Localhost". Must NOT be set for any other type.
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type indicates which kind of seccomp profile will be applied.
|
||||
Valid options are:
|
||||
|
||||
Localhost - a profile defined in a file on the node should be used.
|
||||
RuntimeDefault - the container runtime default profile should be used.
|
||||
Unconfined - no profile should be applied.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
windowsOptions:
|
||||
description: |-
|
||||
The Windows specific settings applied to all containers.
|
||||
If unspecified, the options from the PodSecurityContext will be used.
|
||||
If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
Note that this field cannot be set when spec.os.name is linux.
|
||||
properties:
|
||||
gmsaCredentialSpec:
|
||||
description: |-
|
||||
GMSACredentialSpec is where the GMSA admission webhook
|
||||
(https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the
|
||||
GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
type: string
|
||||
gmsaCredentialSpecName:
|
||||
description: GMSACredentialSpecName is the name of the GMSA credential spec to use.
|
||||
type: string
|
||||
hostProcess:
|
||||
description: |-
|
||||
HostProcess determines if a container should be run as a 'Host Process' container.
|
||||
All of a Pod's containers must have the same effective HostProcess value
|
||||
(it is not allowed to have a mix of HostProcess containers and non-HostProcess containers).
|
||||
In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
type: boolean
|
||||
runAsUserName:
|
||||
description: |-
|
||||
The UserName in Windows to run the entrypoint of the container process.
|
||||
Defaults to the user specified in image metadata if unspecified.
|
||||
May also be set in PodSecurityContext. If set in both SecurityContext and
|
||||
PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
sync:
|
||||
default: {}
|
||||
description: Sync specifies the resources types that will be synced from virtual cluster to host cluster.
|
||||
@@ -1984,6 +2176,12 @@ spec:
|
||||
enabled: false
|
||||
description: Ingresses resources sync configuration.
|
||||
properties:
|
||||
disableTLSSecretTranslation:
|
||||
default: false
|
||||
description: |-
|
||||
DisableTLSSecretTranslation is an on/off switch for translating TLS secrets
|
||||
from virtual cluster to host cluster
|
||||
type: boolean
|
||||
enabled:
|
||||
default: false
|
||||
description: Enabled is an on/off switch for syncing resources.
|
||||
|
||||
@@ -34,7 +34,6 @@ spec:
|
||||
- --agent-virtual-image={{- include "agent.virtual.registry" .}}{{ .Values.agent.virtual.image.repository }}
|
||||
- --agent-virtual-image-pull-policy={{ .Values.agent.virtual.image.pullPolicy }}
|
||||
- --kubelet-port-range={{ .Values.agent.shared.kubeletPortRange }}
|
||||
- --webhook-port-range={{ .Values.agent.shared.webhookPortRange }}
|
||||
{{- range $key, $value := include "image.pullSecrets" (concat .Values.agent.imagePullSecrets .Values.global.imagePullSecrets) | fromYamlArray }}
|
||||
- --agent-image-pull-secret
|
||||
- {{ .name }}
|
||||
@@ -55,7 +54,4 @@ spec:
|
||||
- containerPort: 8080
|
||||
name: https
|
||||
protocol: TCP
|
||||
- containerPort: 9443
|
||||
name: https-webhook
|
||||
protocol: TCP
|
||||
serviceAccountName: {{ include "k3k.serviceAccountName" . }}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: k3k-webhook
|
||||
labels:
|
||||
{{- include "k3k.labels" . | nindent 4 }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
name: https-webhook
|
||||
targetPort: 9443
|
||||
selector:
|
||||
{{- include "k3k.selectorLabels" . | nindent 6 }}
|
||||
@@ -56,7 +56,7 @@ controller:
|
||||
server:
|
||||
imagePullSecrets: []
|
||||
image:
|
||||
registry:
|
||||
registry:
|
||||
repository: "rancher/k3s"
|
||||
pullPolicy: ""
|
||||
|
||||
@@ -74,8 +74,6 @@ agent:
|
||||
|
||||
# Specifies the port range that will be used for k3k-kubelet api if mirrorHostNodes is enabled
|
||||
kubeletPortRange: "50000-51000"
|
||||
# Specifies the port range that will be used for webhook if mirrorHostNodes is enabled
|
||||
webhookPortRange: "51001-52000"
|
||||
|
||||
# configuration related to agent in virtual mode
|
||||
virtual:
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
|
||||
k3kcluster "github.com/rancher/k3k/pkg/controller/cluster"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/agent"
|
||||
)
|
||||
|
||||
var keepData bool
|
||||
@@ -62,11 +61,6 @@ func delete(appCtx *AppContext) func(cmd *cobra.Command, args []string) error {
|
||||
if err := RemoveOwnerReferenceFromSecret(ctx, k3kcluster.TokenSecretName(cluster.Name), client, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// skip removing webhook secret
|
||||
if err := RemoveOwnerReferenceFromSecret(ctx, agent.WebhookSecretName(cluster.Name), client, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
matchingLabels := ctrlclient.MatchingLabels(map[string]string{"cluster": cluster.Name, "role": "server"})
|
||||
listOpts := ctrlclient.ListOptions{Namespace: cluster.Namespace}
|
||||
|
||||
@@ -66,6 +66,10 @@ This includes both node affinity and pod affinity/anti-affinity rules. + | |
|
||||
| *`agentAffinity`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#affinity-v1-core[$$Affinity$$]__ | agentAffinity is the affinity rules for agent pods enforced by the active VirtualClusterPolicy. +
|
||||
This includes both node affinity and pod affinity/anti-affinity rules. + | |
|
||||
| *`sync`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-syncconfig[$$SyncConfig$$]__ | sync is the SyncConfig enforced by the active VirtualClusterPolicy. + | |
|
||||
| *`runtimeClassName`* __string__ | SecurityContext specifies custom SecurityContext to be added +
|
||||
to the agent and server pods of the cluster in virtual or shared mode. + | |
|
||||
| *`securityContext`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core[$$SecurityContext$$]__ | RuntimeClassName specifies alternative runtime class for the +
|
||||
agent and server pods of the cluster in virtual or shared mode. + | |
|
||||
|===
|
||||
|
||||
|
||||
@@ -217,6 +221,11 @@ are mirrored into the virtual cluster. + | |
|
||||
| *`sync`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-syncconfig[$$SyncConfig$$]__ | Sync specifies the resources types that will be synced from virtual cluster to host cluster. + | { } |
|
||||
| *`secretMounts`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-secretmount[$$SecretMount$$] array__ | SecretMounts specifies a list of secrets to mount into server and agent pods. +
|
||||
Each entry defines a secret and its mount path within the pods. + | |
|
||||
| *`securityContext`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core[$$SecurityContext$$]__ | SecurityContext specifies custom SecurityContext to be added +
|
||||
to the agent and server pods of the cluster in virtual or shared mode. +
|
||||
This option will override the SecurityContext set by default for virtual mode. + | |
|
||||
| *`runtimeClassName`* __string__ | RuntimeClassName specifies alternative runtime class for the +
|
||||
agent and server pods of the cluster in virtual or shared mode. + | |
|
||||
|===
|
||||
|
||||
|
||||
@@ -245,7 +254,6 @@ _Appears In:_
|
||||
| *`policy`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-appliedpolicy[$$AppliedPolicy$$]__ | policy represents the status of the policy applied to this cluster. +
|
||||
This field is set by the VirtualClusterPolicy controller. + | |
|
||||
| *`kubeletPort`* __integer__ | KubeletPort specefies the port used by k3k-kubelet in shared mode. + | |
|
||||
| *`webhookPort`* __integer__ | WebhookPort specefies the port used by webhook in k3k-kubelet in shared mode. + | |
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta[$$Condition$$] array__ | Conditions are the individual conditions for the cluster set. + | |
|
||||
| *`phase`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-clusterphase[$$ClusterPhase$$]__ | Phase is a high-level summary of the cluster's current lifecycle state. + | Unknown | Enum: [Pending Provisioning Ready Failed Terminating Unknown] +
|
||||
|
||||
@@ -406,6 +414,8 @@ _Appears In:_
|
||||
| *`enabled`* __boolean__ | Enabled is an on/off switch for syncing resources. + | false |
|
||||
| *`selector`* __object (keys:string, values:string)__ | Selector specifies set of labels of the resources that will be synced, if empty +
|
||||
then all resources of the given type will be synced. + | |
|
||||
| *`disableTLSSecretTranslation`* __boolean__ | DisableTLSSecretTranslation is an on/off switch for translating TLS secrets +
|
||||
from virtual cluster to host cluster + | false |
|
||||
|===
|
||||
|
||||
|
||||
@@ -776,6 +786,10 @@ This includes both node affinity and pod affinity/anti-affinity rules. + | |
|
||||
| *`podSecurityAdmissionLevel`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-podsecurityadmissionlevel[$$PodSecurityAdmissionLevel$$]__ | PodSecurityAdmissionLevel specifies the pod security admission level applied to the pods in the namespace. + | | Enum: [privileged baseline restricted] +
|
||||
|
||||
| *`sync`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-syncconfig[$$SyncConfig$$]__ | Sync specifies the resources types that will be synced from virtual cluster to host cluster. + | { } |
|
||||
| *`runtimeClassName`* __string__ | SecurityContext specifies custom SecurityContext to be added +
|
||||
to the agent and server pods of the cluster in virtual or shared mode. + | |
|
||||
| *`securityContext`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core[$$SecurityContext$$]__ | RuntimeClassName specifies alternative runtime class for the +
|
||||
agent and server pods of the cluster in virtual or shared mode. + | |
|
||||
|===
|
||||
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ _Appears in:_
|
||||
| `serverAffinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#affinity-v1-core)_ | serverAffinity is the affinity rules for server pods enforced by the active VirtualClusterPolicy.<br />This includes both node affinity and pod affinity/anti-affinity rules. | | |
|
||||
| `agentAffinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#affinity-v1-core)_ | agentAffinity is the affinity rules for agent pods enforced by the active VirtualClusterPolicy.<br />This includes both node affinity and pod affinity/anti-affinity rules. | | |
|
||||
| `sync` _[SyncConfig](#syncconfig)_ | sync is the SyncConfig enforced by the active VirtualClusterPolicy. | | |
|
||||
| `runtimeClassName` _string_ | SecurityContext specifies custom SecurityContext to be added<br />to the agent and server pods of the cluster in virtual or shared mode. | | |
|
||||
| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core)_ | RuntimeClassName specifies alternative runtime class for the<br />agent and server pods of the cluster in virtual or shared mode. | | |
|
||||
|
||||
|
||||
#### Cluster
|
||||
@@ -160,6 +162,8 @@ _Appears in:_
|
||||
| `customCAs` _[CustomCAs](#customcas)_ | CustomCAs specifies the cert/key pairs for custom CA certificates. | | |
|
||||
| `sync` _[SyncConfig](#syncconfig)_ | Sync specifies the resources types that will be synced from virtual cluster to host cluster. | \{ \} | |
|
||||
| `secretMounts` _[SecretMount](#secretmount) array_ | SecretMounts specifies a list of secrets to mount into server and agent pods.<br />Each entry defines a secret and its mount path within the pods. | | |
|
||||
| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core)_ | SecurityContext specifies custom SecurityContext to be added<br />to the agent and server pods of the cluster in virtual or shared mode.<br />This option will override the SecurityContext set by default for virtual mode. | | |
|
||||
| `runtimeClassName` _string_ | RuntimeClassName specifies alternative runtime class for the<br />agent and server pods of the cluster in virtual or shared mode. | | |
|
||||
|
||||
|
||||
#### ClusterStatus
|
||||
@@ -183,7 +187,6 @@ _Appears in:_
|
||||
| `policyName` _string_ | PolicyName specifies the virtual cluster policy name bound to the virtual cluster. | | |
|
||||
| `policy` _[AppliedPolicy](#appliedpolicy)_ | policy represents the status of the policy applied to this cluster.<br />This field is set by the VirtualClusterPolicy controller. | | |
|
||||
| `kubeletPort` _integer_ | KubeletPort specefies the port used by k3k-kubelet in shared mode. | | |
|
||||
| `webhookPort` _integer_ | WebhookPort specefies the port used by webhook in k3k-kubelet in shared mode. | | |
|
||||
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions are the individual conditions for the cluster set. | | |
|
||||
| `phase` _[ClusterPhase](#clusterphase)_ | Phase is a high-level summary of the cluster's current lifecycle state. | Unknown | Enum: [Pending Provisioning Ready Failed Terminating Unknown] <br /> |
|
||||
|
||||
@@ -311,6 +314,7 @@ _Appears in:_
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` _boolean_ | Enabled is an on/off switch for syncing resources. | false | |
|
||||
| `selector` _object (keys:string, values:string)_ | Selector specifies set of labels of the resources that will be synced, if empty<br />then all resources of the given type will be synced. | | |
|
||||
| `disableTLSSecretTranslation` _boolean_ | DisableTLSSecretTranslation is an on/off switch for translating TLS secrets<br />from virtual cluster to host cluster | false | |
|
||||
|
||||
|
||||
#### LoadBalancerConfig
|
||||
@@ -586,6 +590,8 @@ _Appears in:_
|
||||
| `disableNetworkPolicy` _boolean_ | DisableNetworkPolicy indicates whether to disable the creation of a default network policy for cluster isolation. | | |
|
||||
| `podSecurityAdmissionLevel` _[PodSecurityAdmissionLevel](#podsecurityadmissionlevel)_ | PodSecurityAdmissionLevel specifies the pod security admission level applied to the pods in the namespace. | | Enum: [privileged baseline restricted] <br /> |
|
||||
| `sync` _[SyncConfig](#syncconfig)_ | Sync specifies the resources types that will be synced from virtual cluster to host cluster. | \{ \} | |
|
||||
| `runtimeClassName` _string_ | SecurityContext specifies custom SecurityContext to be added<br />to the agent and server pods of the cluster in virtual or shared mode. | | |
|
||||
| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#securitycontext-v1-core)_ | RuntimeClassName specifies alternative runtime class for the<br />agent and server pods of the cluster in virtual or shared mode. | | |
|
||||
|
||||
|
||||
#### VirtualClusterPolicyStatus
|
||||
|
||||
@@ -51,9 +51,11 @@ To see all the available Make commands you can run `make help`, i.e:
|
||||
package Package the k3k and k3k-kubelet Docker images
|
||||
push Push the K3k images to the registry
|
||||
test Run all the tests
|
||||
test-unit Run the unit tests (skips the e2e)
|
||||
test-controller Run the controller tests (pkg/controller)
|
||||
test-kubelet-controller Run the controller tests (pkg/controller)
|
||||
test-unit Run the unit tests (skips the e2e and integration tests)
|
||||
test-kubelet Run the k3k-kubelet controller tests (tests/integration/k3k-kubelet)
|
||||
test-policy Run the policy controller tests (tests/integration/policy)
|
||||
test-cluster Run the cluster controller tests (tests/integration/cluster)
|
||||
test-integration Run the controller tests (pkg/controller)
|
||||
test-e2e Run the e2e tests
|
||||
test-cli Run the cli tests
|
||||
generate Generate the CRDs specs
|
||||
|
||||
58
go.mod
58
go.mod
@@ -2,7 +2,7 @@ module github.com/rancher/k3k
|
||||
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.8
|
||||
toolchain go1.25.9
|
||||
|
||||
require (
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
@@ -20,22 +20,22 @@ require (
|
||||
github.com/testcontainers/testcontainers-go v0.41.0
|
||||
github.com/testcontainers/testcontainers-go/modules/k3s v0.41.0
|
||||
github.com/virtual-kubelet/virtual-kubelet v1.12.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.9
|
||||
go.etcd.io/etcd/client/v3 v3.6.9
|
||||
go.etcd.io/etcd/api/v3 v3.6.10
|
||||
go.etcd.io/etcd/client/v3 v3.6.10
|
||||
go.uber.org/zap v1.27.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
helm.sh/helm/v3 v3.18.5
|
||||
k8s.io/api v0.35.1
|
||||
k8s.io/apiextensions-apiserver v0.35.1
|
||||
k8s.io/apimachinery v0.35.1
|
||||
k8s.io/apiserver v0.35.1
|
||||
k8s.io/cli-runtime v0.35.1
|
||||
k8s.io/client-go v0.35.1
|
||||
k8s.io/component-base v0.35.1
|
||||
k8s.io/component-helpers v0.35.1
|
||||
k8s.io/kubectl v0.35.1
|
||||
k8s.io/kubelet v0.35.1
|
||||
k8s.io/kubernetes v1.35.1
|
||||
helm.sh/helm/v3 v3.20.1
|
||||
k8s.io/api v0.35.3
|
||||
k8s.io/apiextensions-apiserver v0.35.3
|
||||
k8s.io/apimachinery v0.35.3
|
||||
k8s.io/apiserver v0.35.3
|
||||
k8s.io/cli-runtime v0.35.3
|
||||
k8s.io/client-go v0.35.3
|
||||
k8s.io/component-base v0.35.3
|
||||
k8s.io/component-helpers v0.35.3
|
||||
k8s.io/kubectl v0.35.3
|
||||
k8s.io/kubelet v0.35.3
|
||||
k8s.io/kubernetes v1.35.3
|
||||
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
|
||||
sigs.k8s.io/controller-runtime v0.23.3
|
||||
)
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
@@ -67,7 +67,7 @@ require (
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.5.2+incompatible // indirect
|
||||
@@ -153,7 +153,7 @@ require (
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rubenv/sql-migrate v1.8.0 // indirect
|
||||
github.com/rubenv/sql-migrate v1.8.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
@@ -169,32 +169,32 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.10 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
@@ -205,7 +205,7 @@ require (
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
k8s.io/controller-manager v0.35.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kms v0.35.1 // indirect
|
||||
k8s.io/kms v0.35.3 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
|
||||
oras.land/oras-go/v2 v2.6.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
|
||||
|
||||
120
go.sum
120
go.sum
@@ -10,8 +10,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
@@ -74,8 +74,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -120,8 +120,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
|
||||
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
||||
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@@ -376,8 +376,8 @@ github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o=
|
||||
github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw=
|
||||
github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=
|
||||
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
@@ -457,12 +457,12 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9 h1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9/go.mod h1:csEk/qTfxKL36NqJdU15Tgtl65A8dyEY2BYo7PRsIwk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 h1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9 h1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9/go.mod h1:KO7H1HLYh1qaljuVZJQwBFk1lRce6pJzt+C81GEnrlM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.10 h1:jlwjtELjA8yi2VWpOFH+0w0lGr3K6mVDyn0RDB9aaAY=
|
||||
go.etcd.io/etcd/api/v3 v3.6.10/go.mod h1:pdV4VeFmvhdNjB4LWRkC8ReLyRBAxUOze3GarMhE2sk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.10 h1:tBT7podcPhuVbCVkAEzx8bC5I+aqxfLwBN8/As1arrA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.10/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y=
|
||||
go.etcd.io/etcd/client/v3 v3.6.10 h1:J598zJ+C/ZPvImypmq5waj84+bovePrlZERHklf34y0=
|
||||
go.etcd.io/etcd/client/v3 v3.6.10/go.mod h1:iHhUDUcEwaKs1YFq3MgmI9U4zhTVasp/vgdVbFf1RS8=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
|
||||
@@ -481,8 +481,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
|
||||
@@ -507,16 +507,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
|
||||
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
|
||||
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
|
||||
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -542,8 +542,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -563,8 +563,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -576,14 +576,14 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -594,8 +594,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -649,40 +649,40 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
helm.sh/helm/v3 v3.18.5 h1:Cc3Z5vd6kDrZq9wO9KxKLNEickiTho6/H/dBNRVSos4=
|
||||
helm.sh/helm/v3 v3.18.5/go.mod h1:L/dXDR2r539oPlFP1PJqKAC1CUgqHJDLkxKpDGrWnyg=
|
||||
helm.sh/helm/v3 v3.20.1 h1:T8PodUaH1UwNvE+imUA2mIKjJItY8g7CVvLVP5g4NzI=
|
||||
helm.sh/helm/v3 v3.20.1/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q=
|
||||
k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM=
|
||||
k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w=
|
||||
k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ=
|
||||
k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU=
|
||||
k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs=
|
||||
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4=
|
||||
k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE=
|
||||
k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw=
|
||||
k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM=
|
||||
k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA=
|
||||
k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE=
|
||||
k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs=
|
||||
k8s.io/component-helpers v0.35.1 h1:vwQ/cAfnVwaPeSXTu4DdK3d3n11Lugc5vMb6EV809ZY=
|
||||
k8s.io/component-helpers v0.35.1/go.mod h1:HQqMwUk68Yyxgj92dJ+J1w/qbx9M0QR0eZ680m/o+Rk=
|
||||
k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=
|
||||
k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=
|
||||
k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU=
|
||||
k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8=
|
||||
k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
|
||||
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.3 h1:D2eIcfJ05hEAEewoSDg+05e0aSRwx8Y4Agvd/wiomUI=
|
||||
k8s.io/apiserver v0.35.3/go.mod h1:JI0n9bHYzSgIxgIrfe21dbduJ9NHzKJ6RchcsmIKWKY=
|
||||
k8s.io/cli-runtime v0.35.3 h1:UZq4ipNimtzBmhN7PPKbfAdqo8quK0H0UdGl6qAQnqI=
|
||||
k8s.io/cli-runtime v0.35.3/go.mod h1:O7MUmCqcKSd5xI+O5X7/pRkB5l0O2NIhOdUVwbHLXu4=
|
||||
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
|
||||
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
|
||||
k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0=
|
||||
k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk=
|
||||
k8s.io/component-helpers v0.35.3 h1:Rl2p3wNMC0YU21rziLkWXavr7MwkB5Td3lNZ/+gYGm8=
|
||||
k8s.io/component-helpers v0.35.3/go.mod h1:8BkyfcBA6XsCtFYxDB+mCfZqM6P39Aco12AKigNn0C8=
|
||||
k8s.io/controller-manager v0.35.1 h1:AKMrGk8sCDa0WtLh+8yfcKck3r/AVw60FOmpak/4fB0=
|
||||
k8s.io/controller-manager v0.35.1/go.mod h1:ifoFum/gxonT7duRuSrNQxU7bctlStGMXraZP5xbaso=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kms v0.35.1 h1:kjv2r9g1mY7uL+l1RhyAZvWVZIA/4qIfBHXyjFGLRhU=
|
||||
k8s.io/kms v0.35.1/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
|
||||
k8s.io/kms v0.35.3 h1:jaxr/7dNqcztGldnfCEZg8DegEOnHV6cfoBC2ACMWEg=
|
||||
k8s.io/kms v0.35.3/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg=
|
||||
k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo=
|
||||
k8s.io/kubelet v0.35.1 h1:8hOxcPmV50p0N24ScAki8cnYPZlrOpjieLk93zOvZMA=
|
||||
k8s.io/kubelet v0.35.1/go.mod h1:yJqkfRRPd56bD1Dp8nOof2AsdSKkdPnkfryNibQZk/8=
|
||||
k8s.io/kubernetes v1.35.1 h1:qmjXSCDPnOuXPuJb5pv+eLzpXhhlD09Jid1pG/OvFU8=
|
||||
k8s.io/kubernetes v1.35.1/go.mod h1:AaPpCpiS8oAqRbEwpY5r3RitLpwpVp5lVXKFkJril58=
|
||||
k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w=
|
||||
k8s.io/kubectl v0.35.3/go.mod h1:GPHxZqRe+u/i3gTBoVQHeIyq2NilfNPj9hDWeuN3x5s=
|
||||
k8s.io/kubelet v0.35.3 h1:Y6b9+U/aTBmou9JZ6qv18O4dpFbJOfl7cBe+ZksT7RY=
|
||||
k8s.io/kubelet v0.35.3/go.mod h1:aWoMogtyUEf/mTl8VjqHbSkW5ZZkB8vTkrg9Fi6TKwE=
|
||||
k8s.io/kubernetes v1.35.3 h1:J3dk2wybKFHwoH4eydDUGHJo4HAD+9CZbSlvk/YQuao=
|
||||
k8s.io/kubernetes v1.35.3/go.mod h1:AaPpCpiS8oAqRbEwpY5r3RitLpwpVp5lVXKFkJril58=
|
||||
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM=
|
||||
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
|
||||
|
||||
@@ -14,7 +14,6 @@ type config struct {
|
||||
HostKubeconfig string `mapstructure:"hostKubeconfig"`
|
||||
VirtKubeconfig string `mapstructure:"virtKubeconfig"`
|
||||
KubeletPort int `mapstructure:"kubeletPort"`
|
||||
WebhookPort int `mapstructure:"webhookPort"`
|
||||
ServerIP string `mapstructure:"serverIP"`
|
||||
Version string `mapstructure:"version"`
|
||||
MirrorHostNodes bool `mapstructure:"mirrorHostNodes"`
|
||||
|
||||
@@ -92,11 +92,20 @@ func (r *IngressReconciler) Reconcile(ctx context.Context, req reconcile.Request
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
appliedSync := cluster.Spec.Sync.DeepCopy()
|
||||
|
||||
// If a policy is applied to the virtual cluster we need to use its SyncConfig, if available
|
||||
if cluster.Status.Policy != nil && cluster.Status.Policy.Sync != nil {
|
||||
appliedSync = cluster.Status.Policy.Sync
|
||||
}
|
||||
|
||||
syncConfig := appliedSync.Ingresses
|
||||
|
||||
if err := r.VirtualClient.Get(ctx, req.NamespacedName, &virtIngress); err != nil {
|
||||
return reconcile.Result{}, ctrlruntimeclient.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
syncedIngress := r.ingress(&virtIngress)
|
||||
syncedIngress := r.ingress(&virtIngress, syncConfig.DisableTLSSecretTranslation)
|
||||
|
||||
if err := controllerutil.SetOwnerReference(&cluster, syncedIngress, r.HostClient.Scheme()); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -143,7 +152,7 @@ func (r *IngressReconciler) Reconcile(ctx context.Context, req reconcile.Request
|
||||
return reconcile.Result{}, r.HostClient.Update(ctx, syncedIngress)
|
||||
}
|
||||
|
||||
func (s *IngressReconciler) ingress(obj *networkingv1.Ingress) *networkingv1.Ingress {
|
||||
func (s *IngressReconciler) ingress(obj *networkingv1.Ingress, disableTLSSecretTranslation bool) *networkingv1.Ingress {
|
||||
hostIngress := obj.DeepCopy()
|
||||
s.Translator.TranslateTo(hostIngress)
|
||||
|
||||
@@ -157,6 +166,17 @@ func (s *IngressReconciler) ingress(obj *networkingv1.Ingress) *networkingv1.Ing
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't sync finalizers to the host
|
||||
|
||||
// TLS Secret translation disable, return early without translating TLS secrets in the ingress spec
|
||||
if disableTLSSecretTranslation {
|
||||
return hostIngress
|
||||
}
|
||||
// ensure tls secrets are also translated
|
||||
for i := range hostIngress.Spec.TLS {
|
||||
if hostIngress.Spec.TLS[i].SecretName != "" {
|
||||
hostIngress.Spec.TLS[i].SecretName = s.Translator.TranslateName(obj.GetNamespace(), hostIngress.Spec.TLS[i].SecretName)
|
||||
}
|
||||
}
|
||||
|
||||
return hostIngress
|
||||
}
|
||||
|
||||
@@ -131,8 +131,19 @@ func (r *PVCReconciler) Reconcile(ctx context.Context, req reconcile.Request) (r
|
||||
}
|
||||
}
|
||||
|
||||
var currentHostPVC v1.PersistentVolumeClaim
|
||||
|
||||
err := r.HostClient.Get(ctx, ctrlruntimeclient.ObjectKeyFromObject(syncedPVC), ¤tHostPVC)
|
||||
if err == nil {
|
||||
log.V(1).Info("persistent volume claim already exist in the host cluster")
|
||||
}
|
||||
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// create the pvc on host
|
||||
log.Info("creating the persistent volume claim for the first time on the host cluster")
|
||||
log.Info("creating the persistent volume claim for the first time in the host cluster")
|
||||
|
||||
// note that we dont need to update the PVC on the host cluster, only syncing the PVC to allow being
|
||||
// handled by the host cluster.
|
||||
|
||||
@@ -2,116 +2,35 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/agent"
|
||||
"github.com/rancher/k3k/pkg/controller"
|
||||
)
|
||||
|
||||
const (
|
||||
webhookName = "podmutating.k3k.io"
|
||||
webhookTimeout = int32(10)
|
||||
webhookPath = "/mutate--v1-pod"
|
||||
FieldpathField = "k3k.io/fieldpath"
|
||||
)
|
||||
const webhookName = "podmutating.k3k.io"
|
||||
|
||||
type webhookHandler struct {
|
||||
client ctrlruntimeclient.Client
|
||||
scheme *runtime.Scheme
|
||||
serviceName string
|
||||
clusterName string
|
||||
clusterNamespace string
|
||||
logger logr.Logger
|
||||
webhookPort int
|
||||
}
|
||||
|
||||
// AddPodMutatingWebhook will add a mutating webhook to the virtual cluster to
|
||||
// modify the nodeName of the created pods with the name of the virtual kubelet node name
|
||||
// as well as remove any status fields of the downward apis env fields
|
||||
func AddPodMutatingWebhook(ctx context.Context, mgr manager.Manager, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, serviceName string, logger logr.Logger, webhookPort int) error {
|
||||
handler := webhookHandler{
|
||||
client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
logger: logger,
|
||||
serviceName: serviceName,
|
||||
clusterName: clusterName,
|
||||
clusterNamespace: clusterNamespace,
|
||||
webhookPort: webhookPort,
|
||||
func RemovePodMutatingWebhook(ctx context.Context, virtualClient, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace string) error {
|
||||
webhookSecret := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: controller.SafeConcatNameWithPrefix(clusterName, "webhook"),
|
||||
Namespace: clusterNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
// create mutating webhook configuration to the cluster
|
||||
config, err := handler.configuration(ctx, hostClient)
|
||||
if err != nil {
|
||||
if err := hostClient.Delete(ctx, webhookSecret); !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handler.client.Create(ctx, config); err != nil {
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// register webhook with the manager
|
||||
return ctrl.NewWebhookManagedBy(mgr, &v1.Pod{}).WithDefaulter(&handler).Complete()
|
||||
}
|
||||
|
||||
func (w *webhookHandler) Default(ctx context.Context, pod *v1.Pod) error {
|
||||
w.logger.Info("mutating webhook request", "pod", pod.Name, "namespace", pod.Namespace)
|
||||
|
||||
// look for status.* fields in the env
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
for i, container := range pod.Spec.Containers {
|
||||
for j, env := range container.Env {
|
||||
if env.ValueFrom == nil || env.ValueFrom.FieldRef == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldPath := env.ValueFrom.FieldRef.FieldPath
|
||||
if strings.Contains(fieldPath, "status.") {
|
||||
annotationKey := fmt.Sprintf("%s_%d_%s", FieldpathField, i, env.Name)
|
||||
pod.Annotations[annotationKey] = fieldPath
|
||||
pod.Spec.Containers[i].Env = removeEnv(pod.Spec.Containers[i].Env, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *webhookHandler) configuration(ctx context.Context, hostClient ctrlruntimeclient.Client) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
|
||||
w.logger.Info("extracting webhook tls from host cluster")
|
||||
|
||||
var webhookTLSSecret v1.Secret
|
||||
|
||||
if err := hostClient.Get(ctx, types.NamespacedName{Name: agent.WebhookSecretName(w.clusterName), Namespace: w.clusterNamespace}, &webhookTLSSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caBundle, ok := webhookTLSSecret.Data["ca.crt"]
|
||||
if !ok {
|
||||
return nil, errors.New("webhook CABundle does not exist in secret")
|
||||
}
|
||||
|
||||
webhookURL := fmt.Sprintf("https://%s:%d%s", w.serviceName, w.webhookPort, webhookPath)
|
||||
|
||||
return &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "admissionregistration.k8s.io/v1",
|
||||
Kind: "MutatingWebhookConfiguration",
|
||||
@@ -119,51 +38,11 @@ func (w *webhookHandler) configuration(ctx context.Context, hostClient ctrlrunti
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: webhookName + "-configuration",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
{
|
||||
Name: webhookName,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNone),
|
||||
TimeoutSeconds: ptr.To(webhookTimeout),
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: ptr.To(webhookURL),
|
||||
CABundle: caBundle,
|
||||
},
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
{
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
"CREATE",
|
||||
},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{""},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods"},
|
||||
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func removeEnv(envs []v1.EnvVar, i int) []v1.EnvVar {
|
||||
envs[i] = envs[len(envs)-1]
|
||||
return envs[:len(envs)-1]
|
||||
}
|
||||
|
||||
func ParseFieldPathAnnotationKey(annotationKey string) (int, string, error) {
|
||||
s := strings.SplitN(annotationKey, "_", 3)
|
||||
if len(s) != 3 {
|
||||
return -1, "", errors.New("fieldpath annotation is not set correctly")
|
||||
}
|
||||
|
||||
containerIndex, err := strconv.Atoi(s[1])
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
if err := virtualClient.Delete(ctx, webhook); !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
envName := s[2]
|
||||
|
||||
return containerIndex, envName, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,13 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
@@ -26,9 +21,7 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
@@ -71,7 +64,7 @@ type kubelet struct {
|
||||
token string
|
||||
}
|
||||
|
||||
func newKubelet(ctx context.Context, c *config, logger logr.Logger) (*kubelet, error) {
|
||||
func newKubelet(ctx context.Context, c *config) (*kubelet, error) {
|
||||
hostConfig, err := clientcmd.BuildConfigFromFlags("", c.HostKubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -84,7 +77,7 @@ func newKubelet(ctx context.Context, c *config, logger logr.Logger) (*kubelet, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
virtConfig, err := virtRestConfig(ctx, c.VirtKubeconfig, hostClient, c.ClusterName, c.ClusterNamespace, c.Token, logger)
|
||||
virtConfig, err := virtRestConfig(ctx, c.VirtKubeconfig, hostClient, c.ClusterName, c.ClusterNamespace, c.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -94,8 +87,6 @@ func newKubelet(ctx context.Context, c *config, logger logr.Logger) (*kubelet, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrl.SetLogger(logger)
|
||||
|
||||
hostMetricsBindAddress := ":8083"
|
||||
virtualMetricsBindAddress := ":8084"
|
||||
|
||||
@@ -128,14 +119,8 @@ func newKubelet(ctx context.Context, c *config, logger logr.Logger) (*kubelet, e
|
||||
return nil, errors.New("unable to add client go types to virtual cluster scheme: " + err.Error())
|
||||
}
|
||||
|
||||
webhookServer := webhook.NewServer(webhook.Options{
|
||||
CertDir: "/opt/rancher/k3k-webhook",
|
||||
Port: c.WebhookPort,
|
||||
})
|
||||
|
||||
virtualMgr, err := ctrl.NewManager(virtConfig, manager.Options{
|
||||
Scheme: virtualScheme,
|
||||
WebhookServer: webhookServer,
|
||||
LeaderElection: true,
|
||||
LeaderElectionNamespace: "kube-system",
|
||||
LeaderElectionID: c.ClusterName,
|
||||
@@ -147,10 +132,10 @@ func newKubelet(ctx context.Context, c *config, logger logr.Logger) (*kubelet, e
|
||||
return nil, errors.New("unable to create controller-runtime mgr for virtual cluster: " + err.Error())
|
||||
}
|
||||
|
||||
logger.Info("adding pod mutating webhook")
|
||||
logger.Info("removing pod mutating webhook")
|
||||
|
||||
if err := k3kwebhook.AddPodMutatingWebhook(ctx, virtualMgr, hostClient, c.ClusterName, c.ClusterNamespace, c.ServiceName, logger, c.WebhookPort); err != nil {
|
||||
return nil, errors.New("unable to add pod mutating webhook for virtual cluster: " + err.Error())
|
||||
if err := k3kwebhook.RemovePodMutatingWebhook(ctx, virtualMgr.GetClient(), hostClient, c.ClusterName, c.ClusterNamespace); err != nil {
|
||||
return nil, errors.New("unable to remove pod mutating webhook for virtual cluster: " + err.Error())
|
||||
}
|
||||
|
||||
if err := addControllers(ctx, hostMgr, virtualMgr, c, hostClient); err != nil {
|
||||
@@ -208,20 +193,6 @@ func clusterIP(ctx context.Context, serviceName, clusterNamespace string, hostCl
|
||||
return service.Spec.ClusterIP, nil
|
||||
}
|
||||
|
||||
func (k *kubelet) registerNode(agentIP string, cfg config) error {
|
||||
providerFunc := k.newProviderFunc(cfg)
|
||||
nodeOpts := k.nodeOpts(cfg.KubeletPort, cfg.ClusterNamespace, cfg.ClusterName, cfg.AgentHostname, agentIP)
|
||||
|
||||
var err error
|
||||
|
||||
k.node, err = nodeutil.NewNode(k.name, providerFunc, nodeutil.WithClient(k.virtClient), nodeOpts)
|
||||
if err != nil {
|
||||
return errors.New("unable to start kubelet: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *kubelet) start(ctx context.Context) {
|
||||
// any one of the following 3 tasks (host manager, virtual manager, node) crashing will stop the
|
||||
// program, and all 3 of them block on start, so we start them here in go-routines
|
||||
@@ -276,8 +247,8 @@ func (k *kubelet) newProviderFunc(cfg config) nodeutil.NewProviderFunc {
|
||||
cfg.AgentHostname,
|
||||
k.port,
|
||||
k.agentIP,
|
||||
k.hostMgr,
|
||||
utilProvider.VirtualClient,
|
||||
utilProvider.Host.Manager,
|
||||
utilProvider.Virtual.Client,
|
||||
k.virtualCluster,
|
||||
cfg.Version,
|
||||
cfg.MirrorHostNodes,
|
||||
@@ -287,32 +258,7 @@ func (k *kubelet) newProviderFunc(cfg config) nodeutil.NewProviderFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kubelet) nodeOpts(srvPort int, namespace, name, hostname, agentIP string) nodeutil.NodeOpt {
|
||||
return func(c *nodeutil.NodeConfig) error {
|
||||
c.HTTPListenAddr = fmt.Sprintf(":%d", srvPort)
|
||||
// set up the routes
|
||||
mux := http.NewServeMux()
|
||||
if err := nodeutil.AttachProviderRoutes(mux)(c); err != nil {
|
||||
return errors.New("unable to attach routes: " + err.Error())
|
||||
}
|
||||
|
||||
c.Handler = mux
|
||||
|
||||
tlsConfig, err := loadTLSConfig(name, namespace, k.name, hostname, k.token, agentIP)
|
||||
if err != nil {
|
||||
return errors.New("unable to get tls config: " + err.Error())
|
||||
}
|
||||
|
||||
c.TLSConfig = tlsConfig
|
||||
|
||||
c.NodeSpec.Labels["kubernetes.io/role"] = "worker"
|
||||
c.NodeSpec.Labels["node-role.kubernetes.io/worker"] = "true"
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func virtRestConfig(ctx context.Context, virtualConfigPath string, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, token string, logger logr.Logger) (*rest.Config, error) {
|
||||
func virtRestConfig(ctx context.Context, virtualConfigPath string, hostClient ctrlruntimeclient.Client, clusterName, clusterNamespace, token string) (*rest.Config, error) {
|
||||
if virtualConfigPath != "" {
|
||||
return clientcmd.BuildConfigFromFlags("", virtualConfigPath)
|
||||
}
|
||||
@@ -384,60 +330,6 @@ func kubeconfigBytes(url string, serverCA, clientCert, clientKey []byte) ([]byte
|
||||
return clientcmd.Write(*config)
|
||||
}
|
||||
|
||||
func loadTLSConfig(clusterName, clusterNamespace, nodeName, hostname, token, agentIP string) (*tls.Config, error) {
|
||||
var b *bootstrap.ControlRuntimeBootstrap
|
||||
|
||||
endpoint := fmt.Sprintf("%s.%s", server.ServiceName(clusterName), clusterNamespace)
|
||||
|
||||
if err := retry.OnError(controller.Backoff, func(err error) bool {
|
||||
return err != nil
|
||||
}, func() error {
|
||||
var err error
|
||||
|
||||
b, err = bootstrap.DecodedBootstrap(token, endpoint)
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, errors.New("unable to decode bootstrap: " + err.Error())
|
||||
}
|
||||
// POD IP
|
||||
podIP := net.ParseIP(os.Getenv("POD_IP"))
|
||||
ip := net.ParseIP(agentIP)
|
||||
|
||||
altNames := certutil.AltNames{
|
||||
DNSNames: []string{hostname},
|
||||
IPs: []net.IP{ip, podIP},
|
||||
}
|
||||
|
||||
cert, key, err := certs.CreateClientCertKey(nodeName, nil, &altNames, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 0, b.ServerCA.Content, b.ServerCAKey.Content)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to get cert and key: " + err.Error())
|
||||
}
|
||||
|
||||
clientCert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to get key pair: " + err.Error())
|
||||
}
|
||||
|
||||
// create rootCA CertPool
|
||||
certs, err := certutil.ParseCertsPEM([]byte(b.ServerCA.Content))
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to create ca certs: " + err.Error())
|
||||
}
|
||||
|
||||
if len(certs) < 1 {
|
||||
return nil, errors.New("ca cert is not parsed correctly")
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(certs[0])
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: pool,
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addControllers(ctx context.Context, hostMgr, virtualMgr manager.Manager, c *config, hostClient ctrlruntimeclient.Client) error {
|
||||
var cluster v1beta1.Cluster
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
ctrlruntimelog "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/rancher/k3k/pkg/log"
|
||||
)
|
||||
@@ -37,7 +36,7 @@ func main() {
|
||||
}
|
||||
|
||||
logger = zapr.NewLogger(log.New(debug, logFormat))
|
||||
ctrlruntimelog.SetLogger(logger)
|
||||
ctrl.SetLogger(logger)
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -52,7 +51,6 @@ func main() {
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.HostKubeconfig, "host-kubeconfig", "", "Path to the host kubeconfig, if empty then virtual-kubelet will use incluster config")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.VirtKubeconfig, "virt-kubeconfig", "", "Path to the k3k cluster kubeconfig, if empty then virtual-kubelet will create its own config from k3k cluster")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.KubeletPort, "kubelet-port", 0, "kubelet API port number")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.WebhookPort, "webhook-port", 0, "Webhook port number")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.ServiceName, "service-name", "", "The service name deployed by the k3k controller")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.AgentHostname, "agent-hostname", "", "Agent Hostname used for TLS SAN for the kubelet server")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.ServerIP, "server-ip", "", "Server IP used for registering the virtual kubelet to the cluster")
|
||||
@@ -66,18 +64,20 @@ func main() {
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return fmt.Errorf("failed to validate config: %w", err)
|
||||
}
|
||||
|
||||
k, err := newKubelet(ctx, &cfg, logger)
|
||||
k, err := newKubelet(ctx, &cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new virtual kubelet instance: %w", err)
|
||||
}
|
||||
|
||||
if err := k.registerNode(k.agentIP, cfg); err != nil {
|
||||
podIP := os.Getenv("POD_IP")
|
||||
|
||||
if err := k.registerNode(k.agentIP, podIP, cfg); err != nil {
|
||||
return fmt.Errorf("failed to register new node: %w", err)
|
||||
}
|
||||
|
||||
|
||||
114
k3k-kubelet/node.go
Normal file
114
k3k-kubelet/node.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
|
||||
"k8s.io/client-go/util/retry"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
|
||||
"github.com/rancher/k3k/pkg/controller"
|
||||
"github.com/rancher/k3k/pkg/controller/certs"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server"
|
||||
"github.com/rancher/k3k/pkg/controller/cluster/server/bootstrap"
|
||||
)
|
||||
|
||||
func (k *kubelet) registerNode(agentIP, podIP string, cfg config) error {
|
||||
tlsConfig, err := loadTLSConfig(cfg, k.name, k.token, agentIP, podIP)
|
||||
if err != nil {
|
||||
return errors.New("unable to get tls config: " + err.Error())
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
node, err := nodeutil.NewNode(
|
||||
k.name,
|
||||
k.newProviderFunc(cfg),
|
||||
nodeutil.WithClient(k.virtClient),
|
||||
nodeutil.AttachProviderRoutes(mux),
|
||||
nodeOpt(mux, tlsConfig, cfg.KubeletPort),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.New("unable to start kubelet: " + err.Error())
|
||||
}
|
||||
|
||||
k.node = node
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nodeOpt(mux *http.ServeMux, tlsConfig *tls.Config, port int) nodeutil.NodeOpt {
|
||||
return func(c *nodeutil.NodeConfig) error {
|
||||
c.Handler = mux
|
||||
c.TLSConfig = tlsConfig
|
||||
|
||||
c.HTTPListenAddr = fmt.Sprintf(":%d", port)
|
||||
|
||||
c.NodeSpec.Labels["kubernetes.io/role"] = "worker"
|
||||
c.NodeSpec.Labels["node-role.kubernetes.io/worker"] = "true"
|
||||
|
||||
c.SkipDownwardAPIResolution = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadTLSConfig(cfg config, nodeName, token, agentIP, podIP string) (*tls.Config, error) {
|
||||
var b *bootstrap.ControlRuntimeBootstrap
|
||||
|
||||
endpoint := fmt.Sprintf("%s.%s", server.ServiceName(cfg.ClusterName), cfg.ClusterNamespace)
|
||||
|
||||
if err := retry.OnError(controller.Backoff, func(err error) bool {
|
||||
return err != nil
|
||||
}, func() error {
|
||||
var err error
|
||||
|
||||
b, err = bootstrap.DecodedBootstrap(token, endpoint)
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, errors.New("unable to decode bootstrap: " + err.Error())
|
||||
}
|
||||
|
||||
altNames := certutil.AltNames{
|
||||
DNSNames: []string{cfg.AgentHostname},
|
||||
IPs: []net.IP{
|
||||
net.ParseIP(agentIP),
|
||||
net.ParseIP(podIP),
|
||||
},
|
||||
}
|
||||
|
||||
cert, key, err := certs.CreateClientCertKey(nodeName, nil, &altNames, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 0, b.ServerCA.Content, b.ServerCAKey.Content)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to get cert and key: " + err.Error())
|
||||
}
|
||||
|
||||
clientCert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to get key pair: " + err.Error())
|
||||
}
|
||||
|
||||
// create rootCA CertPool
|
||||
certs, err := certutil.ParseCertsPEM([]byte(b.ServerCA.Content))
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to create ca certs: " + err.Error())
|
||||
}
|
||||
|
||||
if len(certs) < 1 {
|
||||
return nil, errors.New("ca cert is not parsed correctly")
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(certs[0])
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: pool,
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
}, nil
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import (
|
||||
compbasemetrics "k8s.io/component-base/metrics"
|
||||
v1alpha1stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
|
||||
|
||||
"github.com/rancher/k3k/k3k-kubelet/controller/webhook"
|
||||
"github.com/rancher/k3k/k3k-kubelet/provider/collectors"
|
||||
"github.com/rancher/k3k/k3k-kubelet/translate"
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
|
||||
@@ -47,15 +46,20 @@ import (
|
||||
// check at compile time if the Provider implements the nodeutil.Provider interface
|
||||
var _ nodeutil.Provider = (*Provider)(nil)
|
||||
|
||||
// ClusterContext includes the controller runtime manager and clients
|
||||
type ClusterContext struct {
|
||||
Config rest.Config
|
||||
Client client.Client
|
||||
CoreClient cv1.CoreV1Interface
|
||||
Manager manager.Manager
|
||||
}
|
||||
|
||||
// Provider implements nodetuil.Provider from virtual Kubelet.
|
||||
// TODO: Implement NotifyPods and the required usage so that this can be an async provider
|
||||
type Provider struct {
|
||||
Host ClusterContext
|
||||
Virtual ClusterContext
|
||||
Translator translate.ToHostTranslator
|
||||
HostClient client.Client
|
||||
VirtualClient client.Client
|
||||
VirtualManager manager.Manager
|
||||
ClientConfig rest.Config
|
||||
CoreClient cv1.CoreV1Interface
|
||||
ClusterNamespace string
|
||||
ClusterName string
|
||||
serverIP string
|
||||
@@ -72,18 +76,29 @@ func New(hostConfig rest.Config, hostMgr, virtualMgr manager.Manager, logger log
|
||||
return nil, err
|
||||
}
|
||||
|
||||
virtualCoreClient, err := cv1.NewForConfig(virtualMgr.GetConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
translator := translate.ToHostTranslator{
|
||||
ClusterName: name,
|
||||
ClusterNamespace: namespace,
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
HostClient: hostMgr.GetClient(),
|
||||
VirtualClient: virtualMgr.GetClient(),
|
||||
VirtualManager: virtualMgr,
|
||||
Host: ClusterContext{
|
||||
Manager: hostMgr,
|
||||
Client: hostMgr.GetClient(),
|
||||
CoreClient: coreClient,
|
||||
Config: hostConfig,
|
||||
},
|
||||
Virtual: ClusterContext{
|
||||
Manager: virtualMgr,
|
||||
Client: virtualMgr.GetClient(),
|
||||
CoreClient: virtualCoreClient,
|
||||
},
|
||||
Translator: translator,
|
||||
ClientConfig: hostConfig,
|
||||
CoreClient: coreClient,
|
||||
ClusterNamespace: namespace,
|
||||
ClusterName: name,
|
||||
logger: logger.WithValues("cluster", name),
|
||||
@@ -129,7 +144,7 @@ func (p *Provider) GetContainerLogs(ctx context.Context, namespace, name, contai
|
||||
options.SinceTime = &sinceTime
|
||||
}
|
||||
|
||||
closer, err := p.CoreClient.Pods(p.ClusterNamespace).GetLogs(hostPodName, &options).Stream(ctx)
|
||||
closer, err := p.Host.CoreClient.Pods(p.ClusterNamespace).GetLogs(hostPodName, &options).Stream(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err, "Error getting logs from container")
|
||||
}
|
||||
@@ -145,7 +160,7 @@ func (p *Provider) RunInContainer(ctx context.Context, namespace, name, containe
|
||||
logger := p.logger.WithValues("namespace", namespace, "name", name, "pod", hostPodName, "container", containerName)
|
||||
logger.V(1).Info("RunInContainer")
|
||||
|
||||
req := p.CoreClient.RESTClient().Post().
|
||||
req := p.Host.CoreClient.RESTClient().Post().
|
||||
Resource("pods").
|
||||
Name(hostPodName).
|
||||
Namespace(p.ClusterNamespace).
|
||||
@@ -160,7 +175,7 @@ func (p *Provider) RunInContainer(ctx context.Context, namespace, name, containe
|
||||
Stderr: attach.Stderr() != nil,
|
||||
}, scheme.ParameterCodec)
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(&p.ClientConfig, http.MethodPost, req.URL())
|
||||
exec, err := remotecommand.NewSPDYExecutor(&p.Host.Config, http.MethodPost, req.URL())
|
||||
if err != nil {
|
||||
logger.Error(err, "Error creating SPDY executor")
|
||||
return err
|
||||
@@ -190,7 +205,7 @@ func (p *Provider) AttachToContainer(ctx context.Context, namespace, name, conta
|
||||
logger := p.logger.WithValues("namespace", namespace, "name", name, "pod", hostPodName, "container", containerName)
|
||||
logger.V(1).Info("AttachToContainer")
|
||||
|
||||
req := p.CoreClient.RESTClient().Post().
|
||||
req := p.Host.CoreClient.RESTClient().Post().
|
||||
Resource("pods").
|
||||
Name(hostPodName).
|
||||
Namespace(p.ClusterNamespace).
|
||||
@@ -204,7 +219,7 @@ func (p *Provider) AttachToContainer(ctx context.Context, namespace, name, conta
|
||||
Stderr: attach.Stderr() != nil,
|
||||
}, scheme.ParameterCodec)
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(&p.ClientConfig, http.MethodPost, req.URL())
|
||||
exec, err := remotecommand.NewSPDYExecutor(&p.Host.Config, http.MethodPost, req.URL())
|
||||
if err != nil {
|
||||
logger.Error(err, "Error creating SPDY executor")
|
||||
return err
|
||||
@@ -230,13 +245,13 @@ func (p *Provider) AttachToContainer(ctx context.Context, namespace, name, conta
|
||||
func (p *Provider) GetStatsSummary(ctx context.Context) (*v1alpha1stats.Summary, error) {
|
||||
p.logger.V(1).Info("GetStatsSummary")
|
||||
|
||||
node, err := p.CoreClient.Nodes().Get(ctx, p.agentHostname, metav1.GetOptions{})
|
||||
node, err := p.Host.CoreClient.Nodes().Get(ctx, p.agentHostname, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
p.logger.Error(err, "Unable to get nodes of cluster")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := p.CoreClient.RESTClient().
|
||||
res, err := p.Host.CoreClient.RESTClient().
|
||||
Get().
|
||||
Resource("nodes").
|
||||
Name(node.Name).
|
||||
@@ -323,13 +338,13 @@ func (p *Provider) PortForward(ctx context.Context, namespace, name string, port
|
||||
logger := p.logger.WithValues("namespace", namespace, "name", name, "pod", hostPodName, "port", port)
|
||||
logger.V(1).Info("PortForward")
|
||||
|
||||
req := p.CoreClient.RESTClient().Post().
|
||||
req := p.Host.CoreClient.RESTClient().Post().
|
||||
Resource("pods").
|
||||
Name(hostPodName).
|
||||
Namespace(p.ClusterNamespace).
|
||||
SubResource("portforward")
|
||||
|
||||
transport, upgrader, err := spdy.RoundTripperFor(&p.ClientConfig)
|
||||
transport, upgrader, err := spdy.RoundTripperFor(&p.Host.Config)
|
||||
if err != nil {
|
||||
logger.Error(err, "Error creating RoundTripper for PortForward")
|
||||
return err
|
||||
@@ -373,7 +388,7 @@ func (p *Provider) createPod(ctx context.Context, pod *corev1.Pod) error {
|
||||
}
|
||||
|
||||
var cluster v1beta1.Cluster
|
||||
if err := p.HostClient.Get(ctx, clusterKey, &cluster); err != nil {
|
||||
if err := p.Host.Client.Get(ctx, clusterKey, &cluster); err != nil {
|
||||
logger.Error(err, "Error getting Virtual Cluster definition")
|
||||
return err
|
||||
}
|
||||
@@ -385,7 +400,7 @@ func (p *Provider) createPod(ctx context.Context, pod *corev1.Pod) error {
|
||||
}
|
||||
|
||||
var virtualPod corev1.Pod
|
||||
if err := p.VirtualClient.Get(ctx, key, &virtualPod); err != nil {
|
||||
if err := p.Virtual.Client.Get(ctx, key, &virtualPod); err != nil {
|
||||
logger.Error(err, "Error getting Pod from Virtual Cluster")
|
||||
return err
|
||||
}
|
||||
@@ -398,8 +413,27 @@ func (p *Provider) createPod(ctx context.Context, pod *corev1.Pod) error {
|
||||
|
||||
logger = logger.WithValues("pod", hostPod.Name)
|
||||
|
||||
// Schedule the host pod in the same host node of the virtual kubelet
|
||||
hostPod.Spec.NodeName = p.agentHostname
|
||||
// Clear the NodeName to allow scheduling, and set affinity to prefer scheduling the Pod on the same host node as the virtual kubelet,
|
||||
// unless the user has specified their own affinity, in which case the user's affinity is respected.
|
||||
|
||||
hostPod.Spec.NodeName = ""
|
||||
|
||||
if hostPod.Spec.Affinity == nil {
|
||||
hostPod.Spec.Affinity = &corev1.Affinity{
|
||||
NodeAffinity: &corev1.NodeAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.PreferredSchedulingTerm{{
|
||||
Weight: 100,
|
||||
Preference: corev1.NodeSelectorTerm{
|
||||
MatchExpressions: []corev1.NodeSelectorRequirement{{
|
||||
Key: "kubernetes.io/hostname",
|
||||
Operator: corev1.NodeSelectorOpIn,
|
||||
Values: []string{p.agentHostname},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// The pod's own nodeSelector is ignored.
|
||||
// The final selector is determined by the cluster spec, but overridden by a policy if present.
|
||||
@@ -442,12 +476,6 @@ func (p *Provider) createPod(ctx context.Context, pod *corev1.Pod) error {
|
||||
|
||||
p.configurePodEnvs(hostPod, &virtualPod)
|
||||
|
||||
// fieldpath annotations
|
||||
if err := p.configureFieldPathEnv(&virtualPod, hostPod); err != nil {
|
||||
logger.Error(err, "Unable to fetch fieldpath annotations for pod")
|
||||
return err
|
||||
}
|
||||
|
||||
// volumes will often refer to resources in the virtual cluster
|
||||
// but instead need to refer to the synced host cluster version
|
||||
p.transformVolumes(pod.Namespace, hostPod.Spec.Volumes)
|
||||
@@ -466,12 +494,12 @@ func (p *Provider) createPod(ctx context.Context, pod *corev1.Pod) error {
|
||||
configureNetworking(hostPod, virtualPod.Name, virtualPod.Namespace, p.serverIP, p.dnsIP)
|
||||
|
||||
// set ownerReference to the cluster object
|
||||
if err := controllerutil.SetControllerReference(&cluster, hostPod, p.HostClient.Scheme()); err != nil {
|
||||
if err := controllerutil.SetControllerReference(&cluster, hostPod, p.Host.Client.Scheme()); err != nil {
|
||||
logger.Error(err, "Unable to set owner reference for pod")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.HostClient.Create(ctx, hostPod); err != nil {
|
||||
if err := p.Host.Client.Create(ctx, hostPod); err != nil {
|
||||
logger.Error(err, "Error creating pod on host cluster")
|
||||
return err
|
||||
}
|
||||
@@ -576,14 +604,14 @@ func (p *Provider) updatePod(ctx context.Context, pod *corev1.Pod) error {
|
||||
}
|
||||
|
||||
var hostPod corev1.Pod
|
||||
if err := p.HostClient.Get(ctx, hostKey, &hostPod); err != nil {
|
||||
if err := p.Host.Client.Get(ctx, hostKey, &hostPod); err != nil {
|
||||
logger.Error(err, "Unable to get Pod to update from host cluster")
|
||||
return err
|
||||
}
|
||||
|
||||
updatePod(&hostPod, pod)
|
||||
|
||||
if err := p.HostClient.Update(ctx, &hostPod); err != nil {
|
||||
if err := p.Host.Client.Update(ctx, &hostPod); err != nil {
|
||||
logger.Error(err, "Unable to update Pod in host cluster")
|
||||
return err
|
||||
}
|
||||
@@ -594,7 +622,7 @@ func (p *Provider) updatePod(ctx context.Context, pod *corev1.Pod) error {
|
||||
|
||||
hostPod.Spec.EphemeralContainers = pod.Spec.EphemeralContainers
|
||||
|
||||
if _, err := p.CoreClient.Pods(p.ClusterNamespace).UpdateEphemeralContainers(ctx, hostPod.Name, &hostPod, metav1.UpdateOptions{}); err != nil {
|
||||
if _, err := p.Host.CoreClient.Pods(p.ClusterNamespace).UpdateEphemeralContainers(ctx, hostPod.Name, &hostPod, metav1.UpdateOptions{}); err != nil {
|
||||
logger.Error(err, "Error when updating ephemeral containers in host pod")
|
||||
return err
|
||||
}
|
||||
@@ -612,14 +640,14 @@ func (p *Provider) updatePod(ctx context.Context, pod *corev1.Pod) error {
|
||||
}
|
||||
|
||||
var virtualPod corev1.Pod
|
||||
if err := p.VirtualClient.Get(ctx, key, &virtualPod); err != nil {
|
||||
if err := p.Virtual.Client.Get(ctx, key, &virtualPod); err != nil {
|
||||
logger.Error(err, "Unable to get pod to update from virtual cluster")
|
||||
return err
|
||||
}
|
||||
|
||||
updatePod(&virtualPod, pod)
|
||||
|
||||
if err := p.VirtualClient.Update(ctx, &virtualPod); err != nil {
|
||||
if err := p.Virtual.Client.Update(ctx, &virtualPod); err != nil {
|
||||
logger.Error(err, "Unable to update Pod in virtual cluster")
|
||||
return err
|
||||
}
|
||||
@@ -630,7 +658,7 @@ func (p *Provider) updatePod(ctx context.Context, pod *corev1.Pod) error {
|
||||
|
||||
virtualPod.Spec.EphemeralContainers = pod.Spec.EphemeralContainers
|
||||
|
||||
if _, err := p.CoreClient.Pods(p.ClusterNamespace).UpdateEphemeralContainers(ctx, virtualPod.Name, &virtualPod, metav1.UpdateOptions{}); err != nil {
|
||||
if _, err := p.Host.CoreClient.Pods(p.ClusterNamespace).UpdateEphemeralContainers(ctx, virtualPod.Name, &virtualPod, metav1.UpdateOptions{}); err != nil {
|
||||
logger.Error(err, "Error when updating ephemeral containers in virtual pod")
|
||||
return err
|
||||
}
|
||||
@@ -679,7 +707,7 @@ func (p *Provider) deletePod(ctx context.Context, pod *corev1.Pod) error {
|
||||
logger := p.logger.WithValues("namespace", pod.Namespace, "name", pod.Name, "pod", hostPodName)
|
||||
logger.V(1).Info("DeletePod")
|
||||
|
||||
err := p.CoreClient.Pods(p.ClusterNamespace).Delete(ctx, hostPodName, metav1.DeleteOptions{})
|
||||
err := p.Host.CoreClient.Pods(p.ClusterNamespace).Delete(ctx, hostPodName, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
logger.Info("Pod to delete not found in host cluster")
|
||||
@@ -741,7 +769,7 @@ func (p *Provider) getPodFromHostCluster(ctx context.Context, hostPodName string
|
||||
}
|
||||
|
||||
var pod corev1.Pod
|
||||
if err := p.HostClient.Get(ctx, key, &pod); err != nil {
|
||||
if err := p.Host.Client.Get(ctx, key, &pod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -769,7 +797,7 @@ func (p *Provider) GetPods(ctx context.Context) ([]*corev1.Pod, error) {
|
||||
|
||||
var podList corev1.PodList
|
||||
|
||||
err = p.HostClient.List(ctx, &podList, &client.ListOptions{LabelSelector: selector})
|
||||
err = p.Host.Client.List(ctx, &podList, &client.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
p.logger.Error(err, "Error listing pods from host cluster")
|
||||
return nil, err
|
||||
@@ -946,32 +974,3 @@ func (p *Provider) configureEnvFrom(virtualPod *corev1.Pod, envs []corev1.EnvFro
|
||||
|
||||
return resultingEnvVars
|
||||
}
|
||||
|
||||
// configureFieldPathEnv will retrieve all annotations created by the pod mutating webhook
|
||||
// to assign env fieldpaths to pods, it will also make sure to change the metadata.name and metadata.namespace to the
|
||||
// assigned annotations
|
||||
func (p *Provider) configureFieldPathEnv(pod, tPod *corev1.Pod) error {
|
||||
for name, value := range pod.Annotations {
|
||||
if strings.Contains(name, webhook.FieldpathField) {
|
||||
containerIndex, envName, err := webhook.ParseFieldPathAnnotationKey(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// re-adding these envs to the pod
|
||||
tPod.Spec.Containers[containerIndex].Env = append(tPod.Spec.Containers[containerIndex].Env, corev1.EnvVar{
|
||||
Name: envName,
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
FieldPath: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// removing the annotation from the pod
|
||||
delete(tPod.Annotations, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -20,22 +23,36 @@ const (
|
||||
serviceAccountTokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
)
|
||||
|
||||
// transformTokens copies the serviceaccount tokens used by pod's serviceaccount to a secret on the host cluster and mount it
|
||||
// transformTokens copies the serviceaccount tokens used by virtualPod's serviceaccount to a secret on the host cluster and mount it
|
||||
// to look like the serviceaccount token
|
||||
func (p *Provider) transformTokens(ctx context.Context, pod, tPod *corev1.Pod) error {
|
||||
logger := p.logger.WithValues("namespace", pod.Namespace, "name", pod.Name, "serviceAccountNameod", pod.Spec.ServiceAccountName)
|
||||
logger.V(1).Info("Transforming token")
|
||||
func (p *Provider) transformTokens(ctx context.Context, virtualPod, hostPod *corev1.Pod) error {
|
||||
logger := p.logger.WithValues("namespace", virtualPod.Namespace, "name", virtualPod.Name, "serviceAccountName", virtualPod.Spec.ServiceAccountName)
|
||||
logger.V(1).Info("Transforming service account tokens")
|
||||
|
||||
// transform projected service account token
|
||||
if err := p.transformProjectedTokens(ctx, virtualPod, hostPod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// transform kube-api-access token for all containers in virtualPod
|
||||
if err := p.transformKubeAccessToken(ctx, virtualPod, hostPod); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) transformKubeAccessToken(ctx context.Context, virtualPod, hostPod *corev1.Pod) error {
|
||||
// skip this process if the kube-api-access is already removed from the pod
|
||||
// this is needed in case users already adds their own custom tokens like in rancher imported clusters
|
||||
if !isKubeAccessVolumeFound(pod) {
|
||||
if !hasKubeAccessVolume(virtualPod) {
|
||||
return nil
|
||||
}
|
||||
|
||||
virtualSecretName := k3kcontroller.SafeConcatNameWithPrefix(pod.Spec.ServiceAccountName, "token")
|
||||
virtualSecretName := k3kcontroller.SafeConcatNameWithPrefix(virtualPod.Spec.ServiceAccountName, "token")
|
||||
|
||||
virtualSecret := virtualSecret(virtualSecretName, pod.Namespace, pod.Spec.ServiceAccountName)
|
||||
if err := p.VirtualClient.Create(ctx, virtualSecret); err != nil {
|
||||
virtualSecret := virtualSecret(virtualSecretName, virtualPod.Namespace, virtualPod.Spec.ServiceAccountName)
|
||||
if err := p.Virtual.Client.Create(ctx, virtualSecret); err != nil {
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
@@ -46,7 +63,7 @@ func (p *Provider) transformTokens(ctx context.Context, pod, tPod *corev1.Pod) e
|
||||
Name: virtualSecret.Name,
|
||||
Namespace: virtualSecret.Namespace,
|
||||
}
|
||||
if err := p.VirtualClient.Get(ctx, virtualSecretKey, virtualSecret); err != nil {
|
||||
if err := p.Virtual.Client.Get(ctx, virtualSecretKey, virtualSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
// To avoid race conditions we need to check if the secret's data has been populated
|
||||
@@ -55,23 +72,107 @@ func (p *Provider) transformTokens(ctx context.Context, pod, tPod *corev1.Pod) e
|
||||
return fmt.Errorf("token secret %s/%s data is empty", virtualSecret.Namespace, virtualSecret.Name)
|
||||
}
|
||||
|
||||
hostSecret := virtualSecret.DeepCopy()
|
||||
hostSecret.Type = ""
|
||||
hostSecret.Annotations = make(map[string]string)
|
||||
hostSecret, err := p.translateAndCreateHostTokenSecret(ctx, virtualSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Translator.TranslateTo(hostSecret)
|
||||
hostPod.Spec.ServiceAccountName = ""
|
||||
hostPod.Spec.DeprecatedServiceAccount = ""
|
||||
hostPod.Spec.AutomountServiceAccountToken = ptr.To(false)
|
||||
|
||||
if err := p.HostClient.Create(ctx, hostSecret); err != nil {
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
return err
|
||||
removeKubeAccessVolume(hostPod)
|
||||
addKubeAccessVolume(hostPod, hostSecret.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// transformProjectedTokens will iterate over the host pod projected volume sources
|
||||
// and transform projected tokens to use a requested token secret from the virtual cluster
|
||||
// instead the automatically generated secret on the host cluster.
|
||||
func (p *Provider) transformProjectedTokens(ctx context.Context, virtualPod, hostPod *corev1.Pod) error {
|
||||
for i, volume := range hostPod.Spec.Volumes {
|
||||
if strings.HasPrefix(volume.Name, kubeAPIAccessPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
if volume.Projected == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for j, source := range volume.Projected.Sources {
|
||||
if source.ServiceAccountToken == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
projectedSecret, err := p.requestTokenSecret(ctx, source.ServiceAccountToken, virtualPod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostSecret, err := p.translateAndCreateHostTokenSecret(ctx, projectedSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// replace the projected token volume with a projected secret
|
||||
hostPod.Spec.Volumes[i].Projected.Sources[j].ServiceAccountToken = nil
|
||||
hostPod.Spec.Volumes[i].Projected.Sources[j].Secret = &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: hostSecret.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.translateToken(tPod, hostSecret.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) requestTokenSecret(ctx context.Context, token *corev1.ServiceAccountTokenProjection, virtualPod *corev1.Pod) (*corev1.Secret, error) {
|
||||
namespace := virtualPod.Namespace
|
||||
serviceAccountName := virtualPod.Spec.ServiceAccountName
|
||||
|
||||
var audiences []string
|
||||
if token.Audience != "" {
|
||||
audiences = []string{token.Audience}
|
||||
}
|
||||
|
||||
tokenRequest := &authv1.TokenRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: serviceAccountName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: audiences,
|
||||
ExpirationSeconds: token.ExpirationSeconds,
|
||||
BoundObjectRef: &authv1.BoundObjectReference{
|
||||
Name: virtualPod.Name,
|
||||
UID: virtualPod.UID,
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tokenResp, err := p.Virtual.CoreClient.ServiceAccounts(namespace).CreateToken(ctx, serviceAccountName, tokenRequest, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a virtual secret with that token
|
||||
virtualSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// creating unique name for the virtual secret based on the request attributes
|
||||
Name: generateTokenSecretName(serviceAccountName, token.Path, tokenResp),
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
token.Path: []byte(tokenResp.Status.Token),
|
||||
},
|
||||
}
|
||||
|
||||
return virtualSecret, nil
|
||||
}
|
||||
|
||||
func virtualSecret(name, namespace, serviceAccountName string) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -89,17 +190,25 @@ func virtualSecret(name, namespace, serviceAccountName string) *corev1.Secret {
|
||||
}
|
||||
}
|
||||
|
||||
// translateToken will remove the serviceaccount from the pod and replace the kube-api-access volume
|
||||
// with a custom token volume and mount it to all containers within the pod
|
||||
func (p *Provider) translateToken(pod *corev1.Pod, hostSecretName string) {
|
||||
pod.Spec.ServiceAccountName = ""
|
||||
pod.Spec.DeprecatedServiceAccount = ""
|
||||
pod.Spec.AutomountServiceAccountToken = ptr.To(false)
|
||||
removeKubeAccessVolume(pod)
|
||||
addKubeAccessVolume(pod, hostSecretName)
|
||||
func (p *Provider) translateAndCreateHostTokenSecret(ctx context.Context, projectedToken *corev1.Secret) (*corev1.Secret, error) {
|
||||
hostSecret := projectedToken.DeepCopy()
|
||||
hostSecret.Type = ""
|
||||
hostSecret.Annotations = make(map[string]string)
|
||||
|
||||
p.Translator.TranslateTo(hostSecret)
|
||||
|
||||
data := hostSecret.Data
|
||||
if _, err := controllerutil.CreateOrUpdate(ctx, p.Host.Client, hostSecret, func() error {
|
||||
hostSecret.Data = data
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hostSecret, nil
|
||||
}
|
||||
|
||||
func isKubeAccessVolumeFound(pod *corev1.Pod) bool {
|
||||
func hasKubeAccessVolume(pod *corev1.Pod) bool {
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if strings.HasPrefix(volume.Name, kubeAPIAccessPrefix) {
|
||||
return true
|
||||
@@ -171,4 +280,29 @@ func addKubeAccessVolume(pod *corev1.Pod, hostSecretName string) {
|
||||
MountPath: serviceAccountTokenMountPath,
|
||||
})
|
||||
}
|
||||
|
||||
for i := range pod.Spec.EphemeralContainers {
|
||||
pod.Spec.EphemeralContainers[i].VolumeMounts = append(pod.Spec.EphemeralContainers[i].VolumeMounts, corev1.VolumeMount{
|
||||
Name: tokenVolumeName,
|
||||
MountPath: serviceAccountTokenMountPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateTokenSecretName(serviceAccountName, tokenPath string, tokenReq *authv1.TokenRequest) string {
|
||||
nameComponents := []string{serviceAccountName}
|
||||
|
||||
if tokenReq.Spec.Audiences != nil {
|
||||
nameComponents = append(nameComponents, tokenReq.Spec.Audiences...)
|
||||
}
|
||||
|
||||
if exp := tokenReq.Spec.ExpirationSeconds; exp != nil {
|
||||
nameComponents = append(nameComponents, strconv.FormatInt(*exp, 10))
|
||||
}
|
||||
|
||||
if tokenPath != "" {
|
||||
nameComponents = append(nameComponents, tokenPath)
|
||||
}
|
||||
|
||||
return k3kcontroller.SafeConcatNameWithPrefix(nameComponents...)
|
||||
}
|
||||
|
||||
327
k3k-kubelet/provider/token_test.go
Normal file
327
k3k-kubelet/provider/token_test.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
k3kcontroller "github.com/rancher/k3k/pkg/controller"
|
||||
)
|
||||
|
||||
func Test_hasKubeAccessVolume(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *corev1.Pod
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no volumes",
|
||||
pod: &corev1.Pod{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "volume with kube-api-access prefix",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "kube-api-access-abc123"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "exact kube-api-access name",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "kube-api-access"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "volume without kube-api-access prefix",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "my-volume"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "multiple volumes with one kube-api-access",
|
||||
pod: &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-volume"},
|
||||
{Name: "kube-api-access-xyz"},
|
||||
{Name: "data-volume"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, hasKubeAccessVolume(tt.pod))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_removeKubeAccessVolume(t *testing.T) {
|
||||
t.Run("removes volume and all volume mounts from containers", func(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-volume"},
|
||||
{Name: "kube-api-access-abc"},
|
||||
{Name: "data-volume"},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-volume", MountPath: "/config"},
|
||||
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
||||
{Name: "data-volume", MountPath: "/data"},
|
||||
},
|
||||
},
|
||||
},
|
||||
EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "debug",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
removeKubeAccessVolume(pod)
|
||||
|
||||
// Verify volume was removed
|
||||
assert.Equal(t, 2, len(pod.Spec.Volumes))
|
||||
assert.Equal(t, "config-volume", pod.Spec.Volumes[0].Name)
|
||||
assert.Equal(t, "data-volume", pod.Spec.Volumes[1].Name)
|
||||
|
||||
// Verify init container mount was removed
|
||||
assert.Equal(t, 1, len(pod.Spec.InitContainers[0].VolumeMounts))
|
||||
assert.Equal(t, "config-volume", pod.Spec.InitContainers[0].VolumeMounts[0].Name)
|
||||
|
||||
// Verify container mount was removed
|
||||
assert.Equal(t, 1, len(pod.Spec.Containers[0].VolumeMounts))
|
||||
assert.Equal(t, "data-volume", pod.Spec.Containers[0].VolumeMounts[0].Name)
|
||||
|
||||
// Verify ephemeral container mount was removed
|
||||
assert.Equal(t, 0, len(pod.Spec.EphemeralContainers[0].VolumeMounts))
|
||||
})
|
||||
|
||||
t.Run("no kube-api-access volume present", func(t *testing.T) {
|
||||
pod := &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-volume"},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-volume", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
removeKubeAccessVolume(pod)
|
||||
|
||||
assert.Equal(t, 1, len(pod.Spec.Volumes))
|
||||
assert.Equal(t, "config-volume", pod.Spec.Volumes[0].Name)
|
||||
assert.Equal(t, 1, len(pod.Spec.Containers[0].VolumeMounts))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_addKubeAccessVolume(t *testing.T) {
|
||||
tokenVolumeName := k3kcontroller.SafeConcatNameWithPrefix(kubeAPIAccessPrefix)
|
||||
hostSecretName := "host-secret-token"
|
||||
|
||||
pod := &corev1.Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "existing-volume"},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{Name: "init"},
|
||||
},
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main"},
|
||||
{Name: "sidecar"},
|
||||
},
|
||||
EphemeralContainers: []corev1.EphemeralContainer{
|
||||
{
|
||||
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
||||
Name: "debug",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
addKubeAccessVolume(pod, hostSecretName)
|
||||
|
||||
// Verify volume was added
|
||||
assert.Equal(t, 2, len(pod.Spec.Volumes))
|
||||
addedVol := pod.Spec.Volumes[1]
|
||||
assert.Equal(t, tokenVolumeName, addedVol.Name)
|
||||
assert.Equal(t, hostSecretName, addedVol.Secret.SecretName)
|
||||
|
||||
// Verify init container mount was added
|
||||
assert.Equal(t, 1, len(pod.Spec.InitContainers[0].VolumeMounts))
|
||||
assert.Equal(t, tokenVolumeName, pod.Spec.InitContainers[0].VolumeMounts[0].Name)
|
||||
assert.Equal(t, serviceAccountTokenMountPath, pod.Spec.InitContainers[0].VolumeMounts[0].MountPath)
|
||||
|
||||
// Verify all container mounts were added
|
||||
for _, c := range pod.Spec.Containers {
|
||||
assert.Equal(t, 1, len(c.VolumeMounts), "container %s should have mount", c.Name)
|
||||
assert.Equal(t, tokenVolumeName, c.VolumeMounts[0].Name)
|
||||
assert.Equal(t, serviceAccountTokenMountPath, c.VolumeMounts[0].MountPath)
|
||||
}
|
||||
|
||||
// Verify ephemeral container mounts were added
|
||||
for _, c := range pod.Spec.EphemeralContainers {
|
||||
assert.Equal(t, 1, len(c.VolumeMounts), "ephemeral container %s should have mount", c.Name)
|
||||
assert.Equal(t, tokenVolumeName, c.VolumeMounts[0].Name)
|
||||
assert.Equal(t, serviceAccountTokenMountPath, c.VolumeMounts[0].MountPath)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_virtualSecret(t *testing.T) {
|
||||
s := virtualSecret("my-secret", "my-ns", "my-sa")
|
||||
|
||||
assert.Equal(t, "my-secret", s.Name)
|
||||
assert.Equal(t, "my-ns", s.Namespace)
|
||||
assert.Equal(t, corev1.SecretTypeServiceAccountToken, s.Type)
|
||||
assert.Equal(t, "my-sa", s.Annotations[corev1.ServiceAccountNameKey])
|
||||
assert.Equal(t, "Secret", s.Kind)
|
||||
assert.Equal(t, "v1", s.APIVersion)
|
||||
}
|
||||
|
||||
func Test_generateTokenSecretName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serviceAccountName string
|
||||
tokenPath string
|
||||
tokenReq *authv1.TokenRequest
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no audiences, no expiration, no path",
|
||||
serviceAccountName: "default",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{},
|
||||
},
|
||||
want: "k3k-default",
|
||||
},
|
||||
{
|
||||
name: "no audiences, with expiration",
|
||||
serviceAccountName: "default",
|
||||
tokenPath: "token",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
ExpirationSeconds: ptr.To(int64(3600)),
|
||||
},
|
||||
},
|
||||
want: "k3k-default-3600-token",
|
||||
},
|
||||
{
|
||||
name: "with single audience and expiration",
|
||||
serviceAccountName: "my-sa",
|
||||
tokenPath: "token",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
ExpirationSeconds: ptr.To(int64(3600)),
|
||||
},
|
||||
},
|
||||
want: "k3k-my-sa-api-3600-token",
|
||||
},
|
||||
{
|
||||
name: "with multiple audiences and expiration",
|
||||
serviceAccountName: "my-sa",
|
||||
tokenPath: "token",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{"api", "vault"},
|
||||
ExpirationSeconds: ptr.To(int64(3600)),
|
||||
},
|
||||
},
|
||||
want: "k3k-my-sa-api-vault-3600-token",
|
||||
},
|
||||
{
|
||||
name: "with audiences, no expiration",
|
||||
serviceAccountName: "my-sa",
|
||||
tokenPath: "vault-token",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
},
|
||||
},
|
||||
want: "k3k-my-sa-api-vault-token",
|
||||
},
|
||||
{
|
||||
name: "different paths produce different names",
|
||||
serviceAccountName: "my-sa",
|
||||
tokenPath: "other-path",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{"api"},
|
||||
ExpirationSeconds: ptr.To(int64(3600)),
|
||||
},
|
||||
},
|
||||
want: "k3k-my-sa-api-3600-other-path",
|
||||
},
|
||||
{
|
||||
name: "long name gets truncated with hash",
|
||||
serviceAccountName: "my-very-long-service-account-name",
|
||||
tokenPath: "some-very-long-token-path-value",
|
||||
tokenReq: &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{"some-very-long-audience-string"},
|
||||
ExpirationSeconds: ptr.To(int64(3600)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := generateTokenSecretName(tt.serviceAccountName, tt.tokenPath, tt.tokenReq)
|
||||
if tt.want != "" {
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
|
||||
assert.Less(t, len(got), 64, "name should be under 64 characters")
|
||||
})
|
||||
}
|
||||
}
|
||||
4
main.go
4
main.go
@@ -35,7 +35,6 @@ var (
|
||||
config cluster.Config
|
||||
kubeconfig string
|
||||
kubeletPortRange string
|
||||
webhookPortRange string
|
||||
maxConcurrentReconciles int
|
||||
debug bool
|
||||
logFormat string
|
||||
@@ -72,7 +71,6 @@ func main() {
|
||||
rootCmd.PersistentFlags().StringVar(&config.VirtualAgentImage, "agent-virtual-image", "rancher/k3s", "K3S Virtual Agent image")
|
||||
rootCmd.PersistentFlags().StringVar(&config.VirtualAgentImagePullPolicy, "agent-virtual-image-pull-policy", "", "K3S Virtual Agent image pull policy must be one of Always, IfNotPresent or Never")
|
||||
rootCmd.PersistentFlags().StringVar(&kubeletPortRange, "kubelet-port-range", "50000-51000", "Port Range for k3k kubelet in shared mode")
|
||||
rootCmd.PersistentFlags().StringVar(&webhookPortRange, "webhook-port-range", "51001-52000", "Port Range for k3k kubelet webhook in shared mode")
|
||||
rootCmd.PersistentFlags().StringVar(&config.K3SServerImage, "k3s-server-image", "rancher/k3s", "K3K server image")
|
||||
rootCmd.PersistentFlags().StringVar(&config.K3SServerImagePullPolicy, "k3s-server-image-pull-policy", "", "K3K server image pull policy")
|
||||
rootCmd.PersistentFlags().StringSliceVar(&config.ServerImagePullSecrets, "server-image-pull-secret", nil, "Image pull secret used for for servers")
|
||||
@@ -110,7 +108,7 @@ func run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
runnable := portAllocator.InitPortAllocatorConfig(ctx, mgr.GetClient(), kubeletPortRange, webhookPortRange)
|
||||
runnable := portAllocator.InitPortAllocatorConfig(ctx, mgr.GetClient(), kubeletPortRange)
|
||||
if err := mgr.Add(runnable); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -203,6 +203,19 @@ type ClusterSpec struct {
|
||||
//
|
||||
// +optional
|
||||
SecretMounts []SecretMount `json:"secretMounts,omitempty"`
|
||||
|
||||
// SecurityContext specifies custom SecurityContext to be added
|
||||
// to the agent and server pods of the cluster in virtual or shared mode.
|
||||
// This option will override the SecurityContext set by default for virtual mode.
|
||||
//
|
||||
// +optional
|
||||
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
|
||||
|
||||
// RuntimeClassName specifies alternative runtime class for the
|
||||
// agent and server pods of the cluster in virtual or shared mode.
|
||||
//
|
||||
// +optional
|
||||
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
|
||||
}
|
||||
|
||||
// SecretMount defines a secret to be mounted into server or agent pods,
|
||||
@@ -326,6 +339,13 @@ type IngressSyncConfig struct {
|
||||
//
|
||||
// +optional
|
||||
Selector map[string]string `json:"selector,omitempty"`
|
||||
|
||||
// DisableTLSSecretTranslation is an on/off switch for translating TLS secrets
|
||||
// from virtual cluster to host cluster
|
||||
//
|
||||
// +kubebuilder:default=false
|
||||
// +optional
|
||||
DisableTLSSecretTranslation bool `json:"disableTLSSecretTranslation,omitempty"`
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimSyncConfig specifies the sync options for PersistentVolumeClaims.
|
||||
@@ -581,11 +601,6 @@ type ClusterStatus struct {
|
||||
// +optional
|
||||
KubeletPort int `json:"kubeletPort,omitempty"`
|
||||
|
||||
// WebhookPort specefies the port used by webhook in k3k-kubelet in shared mode.
|
||||
//
|
||||
// +optional
|
||||
WebhookPort int `json:"webhookPort,omitempty"`
|
||||
|
||||
// Conditions are the individual conditions for the cluster set.
|
||||
//
|
||||
// +optional
|
||||
@@ -633,6 +648,18 @@ type AppliedPolicy struct {
|
||||
//
|
||||
// +optional
|
||||
Sync *SyncConfig `json:"sync,omitempty"`
|
||||
|
||||
// SecurityContext specifies custom SecurityContext to be added
|
||||
// to the agent and server pods of the cluster in virtual or shared mode.
|
||||
//
|
||||
// +optional
|
||||
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
|
||||
|
||||
// RuntimeClassName specifies alternative runtime class for the
|
||||
// agent and server pods of the cluster in virtual or shared mode.
|
||||
//
|
||||
// +optional
|
||||
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterPhase is a high-level summary of the cluster's current lifecycle state.
|
||||
@@ -740,6 +767,18 @@ type VirtualClusterPolicySpec struct {
|
||||
// +kubebuilder:default={}
|
||||
// +optional
|
||||
Sync *SyncConfig `json:"sync,omitempty"`
|
||||
|
||||
// SecurityContext specifies custom SecurityContext to be added
|
||||
// to the agent and server pods of the cluster in virtual or shared mode.
|
||||
//
|
||||
// +optional
|
||||
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
|
||||
|
||||
// RuntimeClassName specifies alternative runtime class for the
|
||||
// agent and server pods of the cluster in virtual or shared mode.
|
||||
//
|
||||
// +optional
|
||||
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
// PodSecurityAdmissionLevel is the policy level applied to the pods in the namespace.
|
||||
|
||||
@@ -55,6 +55,16 @@ func (in *AppliedPolicy) DeepCopyInto(out *AppliedPolicy) {
|
||||
*out = new(SyncConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RuntimeClassName != nil {
|
||||
in, out := &in.RuntimeClassName, &out.RuntimeClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.SecurityContext != nil {
|
||||
in, out := &in.SecurityContext, &out.SecurityContext
|
||||
*out = new(v1.SecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppliedPolicy.
|
||||
@@ -232,6 +242,16 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.SecurityContext != nil {
|
||||
in, out := &in.SecurityContext, &out.SecurityContext
|
||||
*out = new(v1.SecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RuntimeClassName != nil {
|
||||
in, out := &in.RuntimeClassName, &out.RuntimeClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.
|
||||
@@ -746,6 +766,16 @@ func (in *VirtualClusterPolicySpec) DeepCopyInto(out *VirtualClusterPolicySpec)
|
||||
*out = new(SyncConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.RuntimeClassName != nil {
|
||||
in, out := &in.RuntimeClassName, &out.RuntimeClassName
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.SecurityContext != nil {
|
||||
in, out := &in.SecurityContext, &out.SecurityContext
|
||||
*out = new(v1.SecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualClusterPolicySpec.
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
const (
|
||||
kubeletPortRangeConfigMapName = "k3k-kubelet-port-range"
|
||||
webhookPortRangeConfigMapName = "k3k-webhook-port-range"
|
||||
|
||||
rangeKey = "range"
|
||||
allocatedPortsKey = "allocatedPorts"
|
||||
@@ -30,7 +29,6 @@ type PortAllocator struct {
|
||||
ctrlruntimeclient.Client
|
||||
|
||||
KubeletCM *v1.ConfigMap
|
||||
WebhookCM *v1.ConfigMap
|
||||
}
|
||||
|
||||
func NewPortAllocator(ctx context.Context, client ctrlruntimeclient.Client) (*PortAllocator, error) {
|
||||
@@ -42,32 +40,20 @@ func NewPortAllocator(ctx context.Context, client ctrlruntimeclient.Client) (*Po
|
||||
return nil, fmt.Errorf("failed to find k3k controller namespace")
|
||||
}
|
||||
|
||||
var kubeletPortRangeCM, webhookPortRangeCM v1.ConfigMap
|
||||
var kubeletPortRangeCM v1.ConfigMap
|
||||
|
||||
kubeletPortRangeCM.Name = kubeletPortRangeConfigMapName
|
||||
kubeletPortRangeCM.Namespace = portRangeConfigMapNamespace
|
||||
|
||||
webhookPortRangeCM.Name = webhookPortRangeConfigMapName
|
||||
webhookPortRangeCM.Namespace = portRangeConfigMapNamespace
|
||||
|
||||
return &PortAllocator{
|
||||
Client: client,
|
||||
KubeletCM: &kubeletPortRangeCM,
|
||||
WebhookCM: &webhookPortRangeCM,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *PortAllocator) InitPortAllocatorConfig(ctx context.Context, client ctrlruntimeclient.Client, kubeletPortRange, webhookPortRange string) manager.Runnable {
|
||||
func (a *PortAllocator) InitPortAllocatorConfig(ctx context.Context, client ctrlruntimeclient.Client, kubeletPortRange string) manager.Runnable {
|
||||
return manager.RunnableFunc(func(ctx context.Context) error {
|
||||
if err := a.getOrCreate(ctx, a.KubeletCM, kubeletPortRange); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.getOrCreate(ctx, a.WebhookCM, webhookPortRange); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return a.getOrCreate(ctx, a.KubeletCM, kubeletPortRange)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,14 +85,6 @@ func (a *PortAllocator) getOrCreate(ctx context.Context, configmap *v1.ConfigMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *PortAllocator) AllocateWebhookPort(ctx context.Context, clusterName, clusterNamespace string) (int, error) {
|
||||
return a.allocatePort(ctx, clusterName, clusterNamespace, a.WebhookCM)
|
||||
}
|
||||
|
||||
func (a *PortAllocator) DeallocateWebhookPort(ctx context.Context, clusterName, clusterNamespace string, webhookPort int) error {
|
||||
return a.deallocatePort(ctx, clusterName, clusterNamespace, a.WebhookCM, webhookPort)
|
||||
}
|
||||
|
||||
func (a *PortAllocator) AllocateKubeletPort(ctx context.Context, clusterName, clusterNamespace string) (int, error) {
|
||||
return a.allocatePort(ctx, clusterName, clusterNamespace, a.KubeletCM)
|
||||
}
|
||||
|
||||
@@ -2,19 +2,14 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
certutil "github.com/rancher/dynamiclistener/cert"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -22,7 +17,6 @@ import (
|
||||
"github.com/rancher/k3k/k3k-kubelet/translate"
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
|
||||
"github.com/rancher/k3k/pkg/controller"
|
||||
"github.com/rancher/k3k/pkg/controller/certs"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -38,11 +32,10 @@ type SharedAgent struct {
|
||||
imageRegistry string
|
||||
token string
|
||||
kubeletPort int
|
||||
webhookPort int
|
||||
imagePullSecrets []string
|
||||
}
|
||||
|
||||
func NewSharedAgent(config *Config, serviceIP, image, imagePullPolicy, token string, kubeletPort, webhookPort int, imagePullSecrets []string) *SharedAgent {
|
||||
func NewSharedAgent(config *Config, serviceIP, image, imagePullPolicy, token string, kubeletPort int, imagePullSecrets []string) *SharedAgent {
|
||||
return &SharedAgent{
|
||||
Config: config,
|
||||
serviceIP: serviceIP,
|
||||
@@ -50,7 +43,6 @@ func NewSharedAgent(config *Config, serviceIP, image, imagePullPolicy, token str
|
||||
imagePullPolicy: imagePullPolicy,
|
||||
token: token,
|
||||
kubeletPort: kubeletPort,
|
||||
webhookPort: webhookPort,
|
||||
imagePullSecrets: imagePullSecrets,
|
||||
}
|
||||
}
|
||||
@@ -68,7 +60,6 @@ func (s *SharedAgent) EnsureResources(ctx context.Context) error {
|
||||
s.service(ctx),
|
||||
s.daemonset(ctx),
|
||||
s.dnsService(ctx),
|
||||
s.webhookTLS(ctx),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to ensure some resources: %w", err)
|
||||
}
|
||||
@@ -81,7 +72,7 @@ func (s *SharedAgent) ensureObject(ctx context.Context, obj ctrlruntimeclient.Ob
|
||||
}
|
||||
|
||||
func (s *SharedAgent) config(ctx context.Context) error {
|
||||
config := sharedAgentData(s.cluster, s.Name(), s.token, s.serviceIP, s.kubeletPort, s.webhookPort)
|
||||
config := sharedAgentData(s.cluster, s.Name(), s.token, s.serviceIP, s.kubeletPort)
|
||||
|
||||
configSecret := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -100,7 +91,7 @@ func (s *SharedAgent) config(ctx context.Context) error {
|
||||
return s.ensureObject(ctx, configSecret)
|
||||
}
|
||||
|
||||
func sharedAgentData(cluster *v1beta1.Cluster, serviceName, token, ip string, kubeletPort, webhookPort int) string {
|
||||
func sharedAgentData(cluster *v1beta1.Cluster, serviceName, token, ip string, kubeletPort int) string {
|
||||
version := cluster.Spec.Version
|
||||
if cluster.Spec.Version == "" {
|
||||
version = cluster.Status.HostVersion
|
||||
@@ -113,9 +104,8 @@ serviceName: %s
|
||||
token: %v
|
||||
mirrorHostNodes: %t
|
||||
version: %s
|
||||
webhookPort: %d
|
||||
kubeletPort: %d`,
|
||||
cluster.Name, cluster.Namespace, ip, serviceName, token, cluster.Spec.MirrorHostNodes, version, webhookPort, kubeletPort)
|
||||
cluster.Name, cluster.Namespace, ip, serviceName, token, cluster.Spec.MirrorHostNodes, version, kubeletPort)
|
||||
}
|
||||
|
||||
func (s *SharedAgent) daemonset(ctx context.Context) error {
|
||||
@@ -196,28 +186,6 @@ func (s *SharedAgent) podSpec(ctx context.Context) v1.PodSpec {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: WebhookSecretName(s.cluster.Name),
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: "tls.crt",
|
||||
Path: "tls.crt",
|
||||
},
|
||||
{
|
||||
Key: "tls.key",
|
||||
Path: "tls.key",
|
||||
},
|
||||
{
|
||||
Key: "ca.crt",
|
||||
Path: "ca.crt",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
@@ -253,11 +221,6 @@ func (s *SharedAgent) podSpec(ctx context.Context) v1.PodSpec {
|
||||
MountPath: "/opt/rancher/k3k/",
|
||||
ReadOnly: false,
|
||||
},
|
||||
{
|
||||
Name: "webhook-certs",
|
||||
MountPath: "/opt/rancher/k3k-webhook",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{
|
||||
@@ -265,11 +228,6 @@ func (s *SharedAgent) podSpec(ctx context.Context) v1.PodSpec {
|
||||
Protocol: v1.ProtocolTCP,
|
||||
ContainerPort: int32(s.kubeletPort),
|
||||
},
|
||||
{
|
||||
Name: "webhook-port",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
ContainerPort: int32(s.webhookPort),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -278,6 +236,23 @@ func (s *SharedAgent) podSpec(ctx context.Context) v1.PodSpec {
|
||||
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, v1.LocalObjectReference{Name: imagePullSecret})
|
||||
}
|
||||
|
||||
securityContext := s.cluster.Spec.SecurityContext
|
||||
if s.cluster.Status.Policy != nil && s.cluster.Status.Policy.SecurityContext != nil {
|
||||
log.V(1).Info("Using securityContext configuration from policy", "policyName", s.cluster.Status.PolicyName, "clusterName", s.cluster.Name)
|
||||
securityContext = s.cluster.Status.Policy.SecurityContext
|
||||
}
|
||||
|
||||
if securityContext != nil {
|
||||
podSpec.Containers[0].SecurityContext = securityContext
|
||||
}
|
||||
|
||||
runtimeClassName := s.cluster.Spec.RuntimeClassName
|
||||
if s.cluster.Status.Policy != nil && s.cluster.Status.Policy.RuntimeClassName != nil {
|
||||
log.V(1).Info("Using runtimeClassName from policy", "policyName", s.cluster.Status.PolicyName, "clusterName", s.cluster.Name)
|
||||
runtimeClassName = s.cluster.Status.Policy.RuntimeClassName
|
||||
}
|
||||
|
||||
podSpec.RuntimeClassName = runtimeClassName
|
||||
return podSpec
|
||||
}
|
||||
|
||||
@@ -304,12 +279,6 @@ func (s *SharedAgent) service(ctx context.Context) error {
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: int32(s.kubeletPort),
|
||||
},
|
||||
{
|
||||
Name: "webhook-server",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: int32(s.webhookPort),
|
||||
TargetPort: intstr.FromInt32(int32(s.webhookPort)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -449,94 +418,3 @@ func (s *SharedAgent) roleBinding(ctx context.Context) error {
|
||||
|
||||
return s.ensureObject(ctx, roleBinding)
|
||||
}
|
||||
|
||||
func (s *SharedAgent) webhookTLS(ctx context.Context) error {
|
||||
webhookSecret := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: WebhookSecretName(s.cluster.Name),
|
||||
Namespace: s.cluster.Namespace,
|
||||
},
|
||||
}
|
||||
|
||||
key := ctrlruntimeclient.ObjectKeyFromObject(webhookSecret)
|
||||
if err := s.client.Get(ctx, key, webhookSecret); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
caPrivateKeyPEM, caCertPEM, err := newWebhookSelfSignedCACerts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
altNames := []string{s.Name(), s.cluster.Name}
|
||||
|
||||
webhookCert, webhookKey, err := newWebhookCerts(s.Name(), altNames, caPrivateKeyPEM, caCertPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
webhookSecret.Data = map[string][]byte{
|
||||
"tls.crt": webhookCert,
|
||||
"tls.key": webhookKey,
|
||||
"ca.crt": caCertPEM,
|
||||
"ca.key": caPrivateKeyPEM,
|
||||
}
|
||||
|
||||
return s.ensureObject(ctx, webhookSecret)
|
||||
}
|
||||
|
||||
// if the webhook secret is found we can skip
|
||||
// we should check for their validity
|
||||
return nil
|
||||
}
|
||||
|
||||
func newWebhookSelfSignedCACerts() ([]byte, []byte, error) {
|
||||
// generate CA CERT/KEY
|
||||
caPrivateKeyPEM, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caPrivateKey, err := certutil.ParsePrivateKeyPEM(caPrivateKeyPEM)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cfg := certutil.Config{
|
||||
CommonName: fmt.Sprintf("k3k-webhook-ca@%d", time.Now().Unix()),
|
||||
}
|
||||
|
||||
caCert, err := certutil.NewSelfSignedCACert(cfg, caPrivateKey.(crypto.Signer))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCertPEM := certutil.EncodeCertPEM(caCert)
|
||||
|
||||
return caPrivateKeyPEM, caCertPEM, nil
|
||||
}
|
||||
|
||||
func newWebhookCerts(commonName string, subAltNames []string, caPrivateKey, caCert []byte) ([]byte, []byte, error) {
|
||||
// generate webhook cert bundle
|
||||
altNames := certs.AddSANs(subAltNames)
|
||||
oneYearExpiration := time.Until(time.Now().AddDate(1, 0, 0))
|
||||
|
||||
return certs.CreateClientCertKey(
|
||||
commonName,
|
||||
nil,
|
||||
&altNames,
|
||||
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
oneYearExpiration,
|
||||
string(caCert),
|
||||
string(caPrivateKey),
|
||||
)
|
||||
}
|
||||
|
||||
func WebhookSecretName(clusterName string) string {
|
||||
return controller.SafeConcatNameWithPrefix(clusterName, "webhook")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
serviceName string
|
||||
ip string
|
||||
kubeletPort int
|
||||
webhookPort int
|
||||
token string
|
||||
}
|
||||
|
||||
@@ -39,7 +38,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
},
|
||||
},
|
||||
kubeletPort: 10250,
|
||||
webhookPort: 9443,
|
||||
ip: "10.0.0.21",
|
||||
serviceName: "service-name",
|
||||
token: "dnjklsdjnksd892389238",
|
||||
@@ -53,7 +51,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
"version": "v1.2.3",
|
||||
"mirrorHostNodes": "false",
|
||||
"kubeletPort": "10250",
|
||||
"webhookPort": "9443",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -73,7 +70,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
},
|
||||
ip: "10.0.0.21",
|
||||
kubeletPort: 10250,
|
||||
webhookPort: 9443,
|
||||
serviceName: "service-name",
|
||||
token: "dnjklsdjnksd892389238",
|
||||
},
|
||||
@@ -86,7 +82,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
"version": "v1.2.3",
|
||||
"mirrorHostNodes": "false",
|
||||
"kubeletPort": "10250",
|
||||
"webhookPort": "9443",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,7 +97,6 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
},
|
||||
},
|
||||
kubeletPort: 10250,
|
||||
webhookPort: 9443,
|
||||
ip: "10.0.0.21",
|
||||
serviceName: "service-name",
|
||||
token: "dnjklsdjnksd892389238",
|
||||
@@ -116,14 +110,13 @@ func Test_sharedAgentData(t *testing.T) {
|
||||
"version": "v1.3.3",
|
||||
"mirrorHostNodes": "false",
|
||||
"kubeletPort": "10250",
|
||||
"webhookPort": "9443",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := sharedAgentData(tt.args.cluster, tt.args.serviceName, tt.args.token, tt.args.ip, tt.args.kubeletPort, tt.args.webhookPort)
|
||||
config := sharedAgentData(tt.args.cluster, tt.args.serviceName, tt.args.token, tt.args.ip, tt.args.kubeletPort)
|
||||
|
||||
data := make(map[string]string)
|
||||
err := yaml.Unmarshal([]byte(config), data)
|
||||
|
||||
@@ -272,5 +272,23 @@ func (v *VirtualAgent) podSpec(ctx context.Context, image, name string) v1.PodSp
|
||||
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, v1.LocalObjectReference{Name: imagePullSecret})
|
||||
}
|
||||
|
||||
securityContext := v.cluster.Spec.SecurityContext
|
||||
if v.cluster.Status.Policy != nil && v.cluster.Status.Policy.SecurityContext != nil {
|
||||
log.V(1).Info("Using securityContext configuration from policy", "policyName", v.cluster.Status.PolicyName, "clusterName", v.cluster.Name)
|
||||
securityContext = v.cluster.Status.Policy.SecurityContext
|
||||
}
|
||||
|
||||
if securityContext != nil {
|
||||
podSpec.Containers[0].SecurityContext = securityContext
|
||||
}
|
||||
|
||||
runtimeClassName := v.cluster.Spec.RuntimeClassName
|
||||
if v.cluster.Status.Policy != nil && v.cluster.Status.Policy.RuntimeClassName != nil {
|
||||
log.V(1).Info("Using runtimeClassName from policy", "policyName", v.cluster.Status.PolicyName, "clusterName", v.cluster.Name)
|
||||
runtimeClassName = v.cluster.Status.Policy.RuntimeClassName
|
||||
}
|
||||
|
||||
podSpec.RuntimeClassName = runtimeClassName
|
||||
|
||||
return podSpec
|
||||
}
|
||||
|
||||
@@ -595,6 +595,10 @@ func (c *ClusterReconciler) ensureNetworkPolicy(ctx context.Context, cluster *v1
|
||||
Except: []string{cluster.Status.ClusterCIDR},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
To: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -602,6 +606,10 @@ func (c *ClusterReconciler) ensureNetworkPolicy(ctx context.Context, cluster *v1
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
To: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -902,7 +910,6 @@ func (c *ClusterReconciler) ensureAgent(ctx context.Context, cluster *v1beta1.Cl
|
||||
} else {
|
||||
// Assign port from pool if shared agent enabled mirroring of host nodes
|
||||
kubeletPort := 10250
|
||||
webhookPort := 9443
|
||||
|
||||
if cluster.Spec.MirrorHostNodes {
|
||||
var err error
|
||||
@@ -913,16 +920,9 @@ func (c *ClusterReconciler) ensureAgent(ctx context.Context, cluster *v1beta1.Cl
|
||||
}
|
||||
|
||||
cluster.Status.KubeletPort = kubeletPort
|
||||
|
||||
webhookPort, err = c.PortAllocator.AllocateWebhookPort(ctx, cluster.Name, cluster.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cluster.Status.WebhookPort = webhookPort
|
||||
}
|
||||
|
||||
agentEnsurer = agent.NewSharedAgent(config, serviceIP, c.SharedAgentImage, c.SharedAgentImagePullPolicy, token, kubeletPort, webhookPort, c.AgentImagePullSecrets)
|
||||
agentEnsurer = agent.NewSharedAgent(config, serviceIP, c.SharedAgentImage, c.SharedAgentImagePullPolicy, token, kubeletPort, c.AgentImagePullSecrets)
|
||||
}
|
||||
|
||||
return agentEnsurer.EnsureResources(ctx)
|
||||
|
||||
@@ -40,17 +40,13 @@ func (c *ClusterReconciler) finalizeCluster(ctx context.Context, cluster *v1beta
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
// Deallocate ports for kubelet and webhook if used
|
||||
// Deallocate ports for kubelet if used
|
||||
if cluster.Spec.Mode == v1beta1.SharedClusterMode && cluster.Spec.MirrorHostNodes {
|
||||
log.V(1).Info("dellocating ports for kubelet and webhook")
|
||||
log.V(1).Info("dellocating ports for kubelet")
|
||||
|
||||
if err := c.PortAllocator.DeallocateKubeletPort(ctx, cluster.Name, cluster.Namespace, cluster.Status.KubeletPort); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if err := c.PortAllocator.DeallocateWebhookPort(ctx, cluster.Name, cluster.Namespace, cluster.Status.WebhookPort); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// delete API server lease
|
||||
|
||||
@@ -246,6 +246,24 @@ func (s *Server) podSpec(ctx context.Context, image, name string, persistent boo
|
||||
}
|
||||
}
|
||||
|
||||
securityContext := s.cluster.Spec.SecurityContext
|
||||
if s.cluster.Status.Policy != nil && s.cluster.Status.Policy.SecurityContext != nil {
|
||||
log.V(1).Info("Using securityContext configuration from policy", "policyName", s.cluster.Status.PolicyName, "clusterName", s.cluster.Name)
|
||||
securityContext = s.cluster.Status.Policy.SecurityContext
|
||||
}
|
||||
|
||||
if securityContext != nil {
|
||||
podSpec.Containers[0].SecurityContext = securityContext
|
||||
}
|
||||
|
||||
runtimeClassName := s.cluster.Spec.RuntimeClassName
|
||||
if s.cluster.Status.Policy != nil && s.cluster.Status.Policy.RuntimeClassName != nil {
|
||||
log.V(1).Info("Using runtimeClassName from policy", "policyName", s.cluster.Status.PolicyName, "clusterName", s.cluster.Name)
|
||||
runtimeClassName = s.cluster.Status.Policy.RuntimeClassName
|
||||
}
|
||||
|
||||
podSpec.RuntimeClassName = runtimeClassName
|
||||
|
||||
// specify resource limits if specified for the servers.
|
||||
if s.cluster.Spec.ServerLimit != nil {
|
||||
podSpec.Containers[0].Resources = v1.ResourceRequirements{
|
||||
|
||||
@@ -473,12 +473,14 @@ func (c *VirtualClusterPolicyReconciler) reconcileClusters(ctx context.Context,
|
||||
origStatus := cluster.Status.DeepCopy()
|
||||
|
||||
cluster.Status.Policy = &v1beta1.AppliedPolicy{
|
||||
Name: policy.Name,
|
||||
PriorityClass: &policy.Spec.DefaultPriorityClass,
|
||||
NodeSelector: policy.Spec.DefaultNodeSelector,
|
||||
Sync: policy.Spec.Sync,
|
||||
ServerAffinity: policy.Spec.DefaultServerAffinity,
|
||||
AgentAffinity: policy.Spec.DefaultAgentAffinity,
|
||||
Name: policy.Name,
|
||||
PriorityClass: &policy.Spec.DefaultPriorityClass,
|
||||
NodeSelector: policy.Spec.DefaultNodeSelector,
|
||||
Sync: policy.Spec.Sync,
|
||||
ServerAffinity: policy.Spec.DefaultServerAffinity,
|
||||
AgentAffinity: policy.Spec.DefaultAgentAffinity,
|
||||
SecurityContext: policy.Spec.SecurityContext,
|
||||
RuntimeClassName: policy.Spec.RuntimeClassName,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(origStatus, &cluster.Status) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3k_test
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -100,7 +100,7 @@ var _ = When("using the k3kcli", Label("cli"), func() {
|
||||
DeleteNamespaces(clusterNamespace)
|
||||
})
|
||||
|
||||
_, stderr, err = K3kcli("cluster", "create", "--version", "v1.35.2-k3s1", clusterName)
|
||||
_, stderr, err = K3kcli("cluster", "create", "--version", k3sVersion, clusterName)
|
||||
Expect(err).To(Not(HaveOccurred()), string(stderr))
|
||||
Expect(stderr).To(ContainSubstring("You can start using the cluster"))
|
||||
})
|
||||
55
tests/cli/common_test.go
Normal file
55
tests/cli/common_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func NewNamespace() *v1.Namespace {
|
||||
GinkgoHelper()
|
||||
|
||||
namespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: "ns-", Labels: map[string]string{"e2e": "true"}}}
|
||||
namespace, err := k8s.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
return namespace
|
||||
}
|
||||
|
||||
func DeleteNamespaces(names ...string) {
|
||||
GinkgoHelper()
|
||||
|
||||
if _, found := os.LookupEnv("KEEP_NAMESPACES"); found {
|
||||
By(fmt.Sprintf("Keeping namespace %v", names))
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(names))
|
||||
|
||||
for _, name := range names {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer GinkgoRecover()
|
||||
|
||||
By(fmt.Sprintf("Deleting namespace %s", name))
|
||||
|
||||
err := k8s.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{
|
||||
GracePeriodSeconds: ptr.To[int64](0),
|
||||
})
|
||||
Expect(client.IgnoreNotFound(err)).To(Not(HaveOccurred()))
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
56
tests/cli/k8s_restclientgetter_test.go
Normal file
56
tests/cli/k8s_restclientgetter_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
memory "k8s.io/client-go/discovery/cached"
|
||||
)
|
||||
|
||||
type RESTClientGetter struct {
|
||||
clientconfig clientcmd.ClientConfig
|
||||
restConfig *rest.Config
|
||||
discoveryClient discovery.CachedDiscoveryInterface
|
||||
}
|
||||
|
||||
func NewRESTClientGetter(kubeconfig []byte) (*RESTClientGetter, error) {
|
||||
clientconfig, err := clientcmd.NewClientConfigFromBytes([]byte(kubeconfig))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restConfig, err := clientconfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dc, err := discovery.NewDiscoveryClientForConfig(restConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RESTClientGetter{
|
||||
clientconfig: clientconfig,
|
||||
restConfig: restConfig,
|
||||
discoveryClient: memory.NewMemCacheClient(dc),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *RESTClientGetter) ToRESTConfig() (*rest.Config, error) {
|
||||
return r.restConfig, nil
|
||||
}
|
||||
|
||||
func (r *RESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
return r.discoveryClient, nil
|
||||
}
|
||||
|
||||
func (r *RESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
return restmapper.NewDeferredDiscoveryRESTMapper(r.discoveryClient), nil
|
||||
}
|
||||
|
||||
func (r *RESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
return r.clientconfig
|
||||
}
|
||||
254
tests/cli/tests_suite_test.go
Normal file
254
tests/cli/tests_suite_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/zapr"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/k3s"
|
||||
"go.uber.org/zap"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
k3kNamespace = "k3k-system"
|
||||
|
||||
k3sVersion = "v1.35.2-k3s1"
|
||||
k3sOldVersion = "v1.35.0-k3s1"
|
||||
)
|
||||
|
||||
func TestTests(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Tests Suite")
|
||||
}
|
||||
|
||||
var (
|
||||
k3sContainer *k3s.K3sContainer
|
||||
restcfg *rest.Config
|
||||
k8s *kubernetes.Clientset
|
||||
k8sClient client.Client
|
||||
kubeconfigPath string
|
||||
helmActionConfig *action.Configuration
|
||||
)
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
ctx := context.Background()
|
||||
|
||||
_, dockerInstallEnabled := os.LookupEnv("K3K_DOCKER_INSTALL")
|
||||
|
||||
if dockerInstallEnabled {
|
||||
repo := os.Getenv("REPO")
|
||||
if repo == "" {
|
||||
repo = "rancher"
|
||||
}
|
||||
|
||||
installK3SDocker(ctx, repo+"/k3k", repo+"/k3k-kubelet")
|
||||
initKubernetesClient()
|
||||
installK3kChart(repo+"/k3k", repo+"/k3k-kubelet")
|
||||
} else {
|
||||
initKubernetesClient()
|
||||
}
|
||||
})
|
||||
|
||||
func initKubernetesClient() {
|
||||
var (
|
||||
err error
|
||||
kubeconfig []byte
|
||||
)
|
||||
|
||||
logger, err := zap.NewDevelopment()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
log.SetLogger(zapr.NewLogger(logger))
|
||||
|
||||
kubeconfigPath := os.Getenv("KUBECONFIG")
|
||||
Expect(kubeconfigPath).To(Not(BeEmpty()))
|
||||
|
||||
kubeconfig, err = os.ReadFile(kubeconfigPath)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
restcfg, err = clientcmd.RESTConfigFromKubeConfig(kubeconfig)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
k8s, err = kubernetes.NewForConfig(restcfg)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
scheme := buildScheme()
|
||||
k8sClient, err = client.New(restcfg, client.Options{Scheme: scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func buildScheme() *runtime.Scheme {
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
err := clientgoscheme.AddToScheme(scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = v1beta1.AddToScheme(scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return scheme
|
||||
}
|
||||
|
||||
func installK3SDocker(ctx context.Context, controllerImage, kubeletImage string) {
|
||||
var (
|
||||
err error
|
||||
kubeconfig []byte
|
||||
)
|
||||
|
||||
k3sHostVersion := os.Getenv("K3S_HOST_VERSION")
|
||||
if k3sHostVersion == "" {
|
||||
k3sHostVersion = k3sVersion
|
||||
}
|
||||
|
||||
k3sHostVersion = strings.ReplaceAll(k3sHostVersion, "+", "-")
|
||||
|
||||
k3sContainer, err = k3s.Run(ctx, "rancher/k3s:"+k3sHostVersion)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
containerIP, err := k3sContainer.ContainerIP(ctx)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
GinkgoWriter.Println("K3s containerIP: " + containerIP)
|
||||
|
||||
kubeconfig, err = k3sContainer.GetKubeConfig(context.Background())
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "kubeconfig-")
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
_, err = tmpFile.Write(kubeconfig)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
Expect(tmpFile.Close()).To(Succeed())
|
||||
kubeconfigPath = tmpFile.Name()
|
||||
|
||||
err = k3sContainer.LoadImages(ctx, controllerImage+":dev", kubeletImage+":dev")
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
DeferCleanup(os.Remove, kubeconfigPath)
|
||||
|
||||
Expect(os.Setenv("KUBECONFIG", kubeconfigPath)).To(Succeed())
|
||||
GinkgoWriter.Printf("KUBECONFIG set to: %s\n", kubeconfigPath)
|
||||
}
|
||||
|
||||
func installK3kChart(controllerImage, kubeletImage string) {
|
||||
pwd, err := os.Getwd()
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
k3kChart, err := loader.Load(path.Join(pwd, "../../charts/k3k"))
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
helmActionConfig = new(action.Configuration)
|
||||
|
||||
kubeconfig, err := os.ReadFile(kubeconfigPath)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
restClientGetter, err := NewRESTClientGetter(kubeconfig)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
err = helmActionConfig.Init(restClientGetter, k3kNamespace, os.Getenv("HELM_DRIVER"), func(format string, v ...any) {
|
||||
GinkgoWriter.Printf("[Helm] "+format+"\n", v...)
|
||||
})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
iCli := action.NewInstall(helmActionConfig)
|
||||
iCli.ReleaseName = "k3k"
|
||||
iCli.Namespace = k3kNamespace
|
||||
iCli.CreateNamespace = true
|
||||
iCli.Timeout = time.Minute
|
||||
iCli.Wait = true
|
||||
|
||||
controllerMap, _ := k3kChart.Values["controller"].(map[string]any)
|
||||
|
||||
extraEnvArray, _ := controllerMap["extraEnv"].([]map[string]any)
|
||||
extraEnvArray = append(extraEnvArray, map[string]any{
|
||||
"name": "DEBUG",
|
||||
"value": "true",
|
||||
})
|
||||
controllerMap["extraEnv"] = extraEnvArray
|
||||
|
||||
imageMap, _ := controllerMap["image"].(map[string]any)
|
||||
maps.Copy(imageMap, map[string]any{
|
||||
"repository": controllerImage,
|
||||
"tag": "dev",
|
||||
"pullPolicy": "IfNotPresent",
|
||||
})
|
||||
|
||||
agentMap, _ := k3kChart.Values["agent"].(map[string]any)
|
||||
sharedAgentMap, _ := agentMap["shared"].(map[string]any)
|
||||
sharedAgentImageMap, _ := sharedAgentMap["image"].(map[string]any)
|
||||
maps.Copy(sharedAgentImageMap, map[string]any{
|
||||
"repository": kubeletImage,
|
||||
"tag": "dev",
|
||||
})
|
||||
|
||||
release, err := iCli.Run(k3kChart, k3kChart.Values)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
GinkgoWriter.Printf("Helm release '%s' installed in '%s' namespace\n", release.Name, release.Namespace)
|
||||
}
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
ctx := context.Background()
|
||||
|
||||
if k3sContainer != nil {
|
||||
// dump k3s logs
|
||||
k3sLogs, err := k3sContainer.Logs(ctx)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
writeLogs("k3s.log", k3sLogs)
|
||||
|
||||
// dump k3k controller logs
|
||||
k3kLogs := getK3kLogs(ctx)
|
||||
writeLogs("k3k.log", k3kLogs)
|
||||
|
||||
testcontainers.CleanupContainer(GinkgoTB(), k3sContainer)
|
||||
}
|
||||
})
|
||||
|
||||
func getK3kLogs(ctx context.Context) io.ReadCloser {
|
||||
var podList v1.PodList
|
||||
|
||||
err := k8sClient.List(ctx, &podList, &client.ListOptions{Namespace: k3kNamespace})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
Expect(podList.Items).NotTo(BeEmpty())
|
||||
|
||||
k3kPod := podList.Items[0]
|
||||
req := k8s.CoreV1().Pods(k3kPod.Namespace).GetLogs(k3kPod.Name, &v1.PodLogOptions{})
|
||||
podLogs, err := req.Stream(ctx)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
return podLogs
|
||||
}
|
||||
|
||||
func writeLogs(filename string, logs io.ReadCloser) {
|
||||
logsStr, err := io.ReadAll(logs)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
tempfile := path.Join(os.TempDir(), filename)
|
||||
err = os.WriteFile(tempfile, []byte(logsStr), 0o644)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
GinkgoWriter.Println("logs written to: " + filename)
|
||||
|
||||
_ = logs.Close()
|
||||
}
|
||||
341
tests/e2e/cluster_app_test.go
Normal file
341
tests/e2e/cluster_app_test.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package k3k_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/rancher/k3k/k3k-kubelet/translate"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Context("In a shared cluster", Label(e2eTestLabel), Ordered, func() {
|
||||
var (
|
||||
virtualCluster *VirtualCluster
|
||||
translator *translate.ToHostTranslator
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
virtualCluster = NewVirtualCluster()
|
||||
translator = translate.NewHostTranslator(virtualCluster.Cluster)
|
||||
|
||||
DeferCleanup(func() {
|
||||
DeleteNamespaces(virtualCluster.Cluster.Namespace)
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a Deployment with a PVC", func() {
|
||||
var (
|
||||
deployment *appsv1.Deployment
|
||||
pvc *v1.PersistentVolumeClaim
|
||||
|
||||
namespace = "default"
|
||||
labels = map[string]string{
|
||||
"app": "k3k-deployment-test-app",
|
||||
}
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
By("Creating the PVC")
|
||||
|
||||
pvc = &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "k3k-test-app-",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.VolumeResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pvc, err = virtualCluster.Client.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
By("Creating the Deployment")
|
||||
|
||||
deployment = &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "k3k-test-app-",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: ptr.To[int32](3),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
},
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "nginx",
|
||||
VolumeMounts: []v1.VolumeMount{{
|
||||
Name: "data-volume",
|
||||
MountPath: "/data",
|
||||
}},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{{
|
||||
Name: "data-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: pvc.Name,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
deployment, err = virtualCluster.Client.AppsV1().Deployments(namespace).Create(ctx, deployment, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
||||
It("should bound the PVC in the virtual cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
virtualPVC, err := virtualCluster.Client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvc.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(virtualPVC.Status.Phase).To(Equal(v1.ClaimBound))
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should bound the PVC in the host cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
hostPVCName := translator.NamespacedName(pvc)
|
||||
|
||||
hostPVC, err := k8s.CoreV1().PersistentVolumeClaims(hostPVCName.Namespace).Get(ctx, hostPVCName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(hostPVC.Status.Phase).To(Equal(v1.ClaimBound))
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should have the Pods running in the virtual cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(deployment.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pods, err := virtualCluster.Client.CoreV1().Pods(namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pods.Items).Should(HaveLen(int(*deployment.Spec.Replicas)))
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should have the Pods running in the host cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(deployment.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pods, err := virtualCluster.Client.CoreV1().Pods(namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pods.Items).Should(HaveLen(int(*deployment.Spec.Replicas)))
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
hostPodName := translator.NamespacedName(&pod)
|
||||
|
||||
pod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a StatefulSet with a PVC", func() {
|
||||
var (
|
||||
statefulSet *appsv1.StatefulSet
|
||||
|
||||
namespace = "default"
|
||||
labels = map[string]string{
|
||||
"app": "k3k-sts-test-app",
|
||||
}
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
namespace := "default"
|
||||
|
||||
By("Creating the StatefulSet")
|
||||
|
||||
statefulSet = &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "k3k-sts-test-app-",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Replicas: ptr.To[int32](3),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
},
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "nginx",
|
||||
VolumeMounts: []v1.VolumeMount{{
|
||||
Name: "www",
|
||||
MountPath: "/usr/share/nginx/html",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
VolumeClaimTemplates: []v1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "www",
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.VolumeResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
statefulSet, err = virtualCluster.Client.AppsV1().StatefulSets(namespace).Create(ctx, statefulSet, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
||||
It("should bound the PVCs in the virtual cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(statefulSet.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pvcs, err := virtualCluster.Client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, pvc := range pvcs.Items {
|
||||
g.Expect(pvc.Status.Phase).To(Equal(v1.ClaimBound))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should bound the PVCs in the host cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(statefulSet.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pvcs, err := virtualCluster.Client.CoreV1().PersistentVolumeClaims(statefulSet.Namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, pvc := range pvcs.Items {
|
||||
hostPVCName := translator.NamespacedName(&pvc)
|
||||
|
||||
hostPVC, err := k8s.CoreV1().PersistentVolumeClaims(hostPVCName.Namespace).Get(ctx, hostPVCName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(hostPVC.Status.Phase).To(Equal(v1.ClaimBound))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should have the Pods running in the virtual cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(statefulSet.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pods, err := virtualCluster.Client.CoreV1().Pods(namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pods.Items).Should(HaveLen(int(*statefulSet.Spec.Replicas)))
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should have the Pods running in the host cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
labelSelector := metav1.FormatLabelSelector(statefulSet.Spec.Selector)
|
||||
listOpts := metav1.ListOptions{LabelSelector: labelSelector}
|
||||
|
||||
pods, err := virtualCluster.Client.CoreV1().Pods(namespace).List(ctx, listOpts)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pods.Items).Should(HaveLen(int(*statefulSet.Spec.Replicas)))
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
hostPodName := translator.NamespacedName(&pod)
|
||||
|
||||
pod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
}
|
||||
}).
|
||||
WithPolling(time.Second * 3).
|
||||
WithTimeout(time.Minute * 3).
|
||||
Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -20,16 +20,132 @@ import (
|
||||
)
|
||||
|
||||
var _ = Context("In a shared cluster", Label(e2eTestLabel), Ordered, func() {
|
||||
var virtualCluster *VirtualCluster
|
||||
var (
|
||||
virtualCluster *VirtualCluster
|
||||
translator *translate.ToHostTranslator
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
virtualCluster = NewVirtualCluster()
|
||||
translator = translate.NewHostTranslator(virtualCluster.Cluster)
|
||||
|
||||
DeferCleanup(func() {
|
||||
DeleteNamespaces(virtualCluster.Cluster.Namespace)
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a Pod without any Affinity", func() {
|
||||
var pod *v1.Pod
|
||||
|
||||
BeforeAll(func() {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pod = &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "nginx-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Name: "nginx",
|
||||
Image: "nginx",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
pod, err = virtualCluster.Client.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
||||
It("should have the default Affinity", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
hostPodName := translator.NamespacedName(pod)
|
||||
|
||||
hostPod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(hostPod.Spec.Affinity).To(Not(BeNil()))
|
||||
g.Expect(hostPod.Spec.Affinity.NodeAffinity).To(Not(BeNil()))
|
||||
g.Expect(hostPod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution).To(Not(BeNil()))
|
||||
|
||||
preferredScheduling := hostPod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution
|
||||
g.Expect(preferredScheduling).To(Not(BeEmpty()))
|
||||
g.Expect(preferredScheduling[0].Weight).To(Equal(int32(100)))
|
||||
g.Expect(preferredScheduling[0].Preference.MatchExpressions).To(Not(BeEmpty()))
|
||||
g.Expect(preferredScheduling[0].Preference.MatchExpressions[0].Key).To(Equal("kubernetes.io/hostname"))
|
||||
}).
|
||||
WithPolling(time.Second).
|
||||
WithTimeout(time.Minute).
|
||||
Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a Pod with an Affinity", func() {
|
||||
var pod *v1.Pod
|
||||
|
||||
BeforeAll(func() {
|
||||
var err error
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
pod = &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "nginx-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Name: "nginx",
|
||||
Image: "nginx",
|
||||
}},
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{{
|
||||
Key: "kubernetes.io/hostname",
|
||||
Operator: v1.NodeSelectorOpNotIn,
|
||||
Values: []string{"fake"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod, err = virtualCluster.Client.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
||||
It("should not have the default Affinity", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
hostPodName := translator.NamespacedName(pod)
|
||||
|
||||
hostPod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(hostPod.Spec.Affinity).To(Not(BeNil()))
|
||||
g.Expect(hostPod.Spec.Affinity.NodeAffinity).To(Not(BeNil()))
|
||||
g.Expect(hostPod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution).To(Not(BeNil()))
|
||||
|
||||
requiredScheduling := hostPod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||
g.Expect(requiredScheduling).To(Not(BeNil()))
|
||||
g.Expect(requiredScheduling.NodeSelectorTerms).To(Not(BeEmpty()))
|
||||
g.Expect(requiredScheduling.NodeSelectorTerms[0].MatchExpressions).To(Not(BeEmpty()))
|
||||
g.Expect(requiredScheduling.NodeSelectorTerms[0].MatchExpressions[0].Key).To(Equal("kubernetes.io/hostname"))
|
||||
g.Expect(requiredScheduling.NodeSelectorTerms[0].MatchExpressions[0].Values).To(ContainElement("fake"))
|
||||
}).
|
||||
WithPolling(time.Second).
|
||||
WithTimeout(time.Minute).
|
||||
Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a Pod with an invalid configuration", func() {
|
||||
var virtualPod *v1.Pod
|
||||
|
||||
@@ -140,7 +256,6 @@ var _ = Context("In a shared cluster", Label(e2eTestLabel), Ordered, func() {
|
||||
By("Checking the container status of the Pod in the Host Cluster")
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
translator := translate.NewHostTranslator(virtualCluster.Cluster)
|
||||
hostPodName := translator.NamespacedName(virtualPod)
|
||||
|
||||
pod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
@@ -207,7 +322,6 @@ var _ = Context("In a shared cluster", Label(e2eTestLabel), Ordered, func() {
|
||||
By("Checking the status of the Pod in the Host Cluster")
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
translator := translate.NewHostTranslator(virtualCluster.Cluster)
|
||||
hostPodName := translator.NamespacedName(virtualPod)
|
||||
|
||||
hPod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
@@ -223,6 +337,82 @@ var _ = Context("In a shared cluster", Label(e2eTestLabel), Ordered, func() {
|
||||
})
|
||||
})
|
||||
|
||||
When("creating a Pod with downward API variables in environment variable", func() {
|
||||
var virtualPod *v1.Pod
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx := context.Background()
|
||||
|
||||
var err error
|
||||
|
||||
p := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "nginx-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "nginx",
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "POD_NAME",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
FieldPath: "metadata.name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "STATUS_POD_IP",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
FieldPath: "status.podIP",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
virtualPod, err = virtualCluster.Client.CoreV1().Pods(p.Namespace).Create(ctx, p, metav1.CreateOptions{})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
||||
It("should be scheduled and running in the virtual cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
pod, err := virtualCluster.Client.CoreV1().Pods(virtualPod.Namespace).Get(ctx, virtualPod.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
g.Expect(pod.Status.PodIP).NotTo(BeEmpty())
|
||||
}).
|
||||
WithPolling(time.Second).
|
||||
WithTimeout(time.Minute).
|
||||
Should(Succeed())
|
||||
})
|
||||
|
||||
It("should be scheduled and running in the host cluster", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
translator := translate.NewHostTranslator(virtualCluster.Cluster)
|
||||
hostPodName := translator.NamespacedName(virtualPod)
|
||||
|
||||
pod, err := k8s.CoreV1().Pods(hostPodName.Namespace).Get(ctx, hostPodName.Name, metav1.GetOptions{})
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(pod.Status.Phase).To(Equal(v1.PodRunning))
|
||||
}).
|
||||
WithPolling(time.Second).
|
||||
WithTimeout(time.Minute).
|
||||
Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("installing the nginx-ingress controller", func() {
|
||||
BeforeAll(func() {
|
||||
By("installing the nginx-ingress controller")
|
||||
@@ -412,13 +412,15 @@ func restartServerPod(ctx context.Context, virtualCluster *VirtualCluster) {
|
||||
By("Deleting server pod")
|
||||
|
||||
// check that the server pods restarted
|
||||
Eventually(func() any {
|
||||
Eventually(func(g Gomega) {
|
||||
serverPods := listServerPods(ctx, virtualCluster)
|
||||
|
||||
Expect(len(serverPods)).To(Equal(1))
|
||||
|
||||
return serverPods[0].DeletionTimestamp
|
||||
}).WithTimeout(60 * time.Second).WithPolling(time.Second * 5).Should(BeNil())
|
||||
g.Expect(serverPods).To(HaveLen(1))
|
||||
g.Expect(serverPods[0].DeletionTimestamp).To(Not(BeNil()))
|
||||
}).
|
||||
WithTimeout(time.Minute * 2).
|
||||
WithPolling(time.Second * 5).
|
||||
Should(Succeed())
|
||||
}
|
||||
|
||||
func listServerPods(ctx context.Context, virtualCluster *VirtualCluster) []v1.Pod {
|
||||
@@ -362,6 +362,7 @@ func dumpK3kCoverageData(ctx context.Context, folder string) {
|
||||
|
||||
err := k8sClient.List(ctx, &podList, &client.ListOptions{Namespace: k3kNamespace})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
Expect(podList.Items).NotTo(BeEmpty())
|
||||
|
||||
k3kPod := podList.Items[0]
|
||||
k3kContainerName := k3kPod.Spec.Containers[0].Name
|
||||
@@ -455,6 +456,7 @@ func getK3kLogs(ctx context.Context) io.ReadCloser {
|
||||
|
||||
err := k8sClient.List(ctx, &podList, &client.ListOptions{Namespace: k3kNamespace})
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
Expect(podList.Items).NotTo(BeEmpty())
|
||||
|
||||
k3kPod := podList.Items[0]
|
||||
req := k8s.CoreV1().Pods(k3kPod.Namespace).GetLogs(k3kPod.Name, &v1.PodLogOptions{Previous: true})
|
||||
@@ -68,7 +68,7 @@ var _ = BeforeSuite(func() {
|
||||
portAllocator, err := agent.NewPortAllocator(ctx, mgr.GetClient())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = mgr.Add(portAllocator.InitPortAllocatorConfig(ctx, mgr.GetClient(), "50000-51000", "51001-52000"))
|
||||
err = mgr.Add(portAllocator.InitPortAllocatorConfig(ctx, mgr.GetClient(), "50000-51000"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
@@ -47,6 +47,9 @@ var IngressTests = func() {
|
||||
Ingresses: v1beta1.IngressSyncConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
Secrets: v1beta1.SecretSyncConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -55,6 +58,8 @@ var IngressTests = func() {
|
||||
|
||||
err = syncer.AddIngressSyncer(ctx, virtManager, hostManager, cluster.Name, cluster.Namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = syncer.AddSecretSyncer(ctx, virtManager, hostManager, cluster.Name, cluster.Namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@@ -292,6 +297,194 @@ var IngressTests = func() {
|
||||
Should(BeTrue())
|
||||
})
|
||||
|
||||
It("will sync an Ingress with a TLS secret", func() {
|
||||
ctx := context.Background()
|
||||
ingressSecret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "ingress-secret-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"ca.crt": []byte("value"),
|
||||
"tls.crt": []byte("value"),
|
||||
"tls.key": []byte("value"),
|
||||
},
|
||||
}
|
||||
|
||||
err := virtTestEnv.k8sClient.Create(ctx, ingressSecret)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ingress := &networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "ingress-",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
Rules: []networkingv1.IngressRule{
|
||||
{
|
||||
Host: "test.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
PathType: ptr.To(networkingv1.PathTypePrefix),
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
Name: "test-service",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Name: "test-port",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: []networkingv1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.com"},
|
||||
SecretName: ingressSecret.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = virtTestEnv.k8sClient.Create(ctx, ingress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By(fmt.Sprintf("Created Ingress %s in virtual cluster", ingress.Name))
|
||||
|
||||
var hostIngress networkingv1.Ingress
|
||||
|
||||
var hostSecret v1.Secret
|
||||
|
||||
hostIngressName := translateName(cluster, ingress.Namespace, ingress.Name)
|
||||
|
||||
hostSecretName := translateName(cluster, ingress.Namespace, ingressSecret.Name)
|
||||
|
||||
Eventually(func() error {
|
||||
key := client.ObjectKey{Name: hostIngressName, Namespace: namespace}
|
||||
return hostTestEnv.k8sClient.Get(ctx, key, &hostIngress)
|
||||
}).
|
||||
WithPolling(time.Millisecond * 300).
|
||||
WithTimeout(time.Second * 10).
|
||||
Should(BeNil())
|
||||
|
||||
Eventually(func() error {
|
||||
key := client.ObjectKey{Name: hostSecretName, Namespace: namespace}
|
||||
return hostTestEnv.k8sClient.Get(ctx, key, &hostSecret)
|
||||
}).
|
||||
WithPolling(time.Millisecond * 300).
|
||||
WithTimeout(time.Second * 10).
|
||||
Should(BeNil())
|
||||
|
||||
// Verify Ingress in host cluster does reference the translated secret since TLS secret translation is enabled
|
||||
Expect(hostIngress.Spec.TLS[0].SecretName).To(Equal(hostSecretName))
|
||||
})
|
||||
|
||||
It("will not sync an Ingress with a TLS secret", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
cluster.Spec.Sync.Ingresses.DisableTLSSecretTranslation = true
|
||||
err := hostTestEnv.k8sClient.Update(ctx, &cluster)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ingressSecret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "ingress-secret-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"ca.crt": []byte("value"),
|
||||
"tls.crt": []byte("value"),
|
||||
"tls.key": []byte("value"),
|
||||
},
|
||||
}
|
||||
|
||||
err = virtTestEnv.k8sClient.Create(ctx, ingressSecret)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ingress := &networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "ingress-",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
Rules: []networkingv1.IngressRule{
|
||||
{
|
||||
Host: "test.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
PathType: ptr.To(networkingv1.PathTypePrefix),
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
Name: "test-service",
|
||||
Port: networkingv1.ServiceBackendPort{
|
||||
Name: "test-port",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: []networkingv1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"test.com"},
|
||||
SecretName: ingressSecret.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = virtTestEnv.k8sClient.Create(ctx, ingress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By(fmt.Sprintf("Created Ingress %s in virtual cluster", ingress.Name))
|
||||
|
||||
var hostIngress networkingv1.Ingress
|
||||
|
||||
var hostSecret v1.Secret
|
||||
|
||||
hostIngressName := translateName(cluster, ingress.Namespace, ingress.Name)
|
||||
|
||||
hostSecretName := translateName(cluster, ingress.Namespace, ingressSecret.Name)
|
||||
|
||||
Eventually(func() error {
|
||||
key := client.ObjectKey{Name: hostIngressName, Namespace: namespace}
|
||||
return hostTestEnv.k8sClient.Get(ctx, key, &hostIngress)
|
||||
}).
|
||||
WithPolling(time.Millisecond * 300).
|
||||
WithTimeout(time.Second * 10).
|
||||
Should(BeNil())
|
||||
|
||||
// Secret will be translated to host cluster but the Ingress will not reference it since TLS secret translation is disabled
|
||||
Eventually(func() error {
|
||||
key := client.ObjectKey{Name: hostSecretName, Namespace: namespace}
|
||||
return hostTestEnv.k8sClient.Get(ctx, key, &hostSecret)
|
||||
}).
|
||||
WithPolling(time.Millisecond * 300).
|
||||
WithTimeout(time.Second * 10).
|
||||
Should(BeNil())
|
||||
|
||||
// Verify Ingress in host cluster has guest cluster secret reference as translation has not been performed
|
||||
Expect(hostIngress.Spec.TLS[0].SecretName).To(Equal(ingressSecret.Name))
|
||||
})
|
||||
|
||||
It("will not sync an Ingress if disabled", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user