├── .codecov.yml ├── .dockerignore ├── .github └── workflows │ ├── chart-upload.yml │ ├── dco.yml │ ├── go-postsubmit.yml │ ├── go-presubmit.yml │ └── go-release.yml ├── .gitignore ├── .linelint.yml ├── CHANGELOG └── CHANGELOG-0.3.0.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DCO ├── FQA.md ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── SECURITY.md ├── charts └── cluster-proxy │ ├── Chart.yaml │ ├── crds │ ├── managedproxyconfigurations.yaml │ └── managedproxyserviceresolvers.yaml │ ├── templates │ ├── clustermanagementaddon.yaml │ ├── clusterrole.yaml │ ├── clusterrolebinding.yaml │ ├── clustersetbinding.yaml │ ├── managedproxyconfiguration.yaml │ ├── manager-deployment.yaml │ ├── placement.yaml │ ├── role.yaml │ ├── rolebinding.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── client └── client.go ├── cmd ├── Dockerfile ├── addon-agent │ └── main.go └── addon-manager │ └── main.go ├── examples ├── access-exported-services.md ├── access-prometheus.md └── test-client.md ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── crd │ ├── addon │ │ ├── clustermanagementaddon.yaml │ │ └── managedclusteraddon.yaml │ ├── bases │ │ ├── cluster.open-cluster-management.io_managedclustersets.yaml │ │ ├── proxy.open-cluster-management.io_managedproxyconfigurations.yaml │ │ └── proxy.open-cluster-management.io_managedproxyserviceresolvers.yaml │ ├── cluster │ │ └── managedclusterset.yaml │ ├── registration │ │ └── managedcluster.yaml │ └── work │ │ └── manifestwork.yaml ├── picture │ └── arch.png └── samples │ ├── clustermanagementaddon.yaml │ ├── managedcluster.yaml │ ├── managedclusteraddon.yaml │ └── managedproxyconfiguration.yaml ├── pkg ├── apis │ ├── doc.go │ └── proxy │ │ ├── doc.go │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── groupversion_info.go │ │ ├── managedproxyconfiguration_types.go │ │ ├── managedproxyserviceresolver_types.go │ │ └── zz_generated.deepcopy.go ├── common │ ├── constants.go │ └── help.go ├── config │ ├── agent.go │ └── agent_test.go ├── generated │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── proxy │ │ │ └── v1alpha1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_managedproxyconfiguration.go │ │ │ ├── fake_managedproxyserviceresolver.go │ │ │ └── fake_proxy_client.go │ │ │ ├── generated_expansion.go │ │ │ ├── managedproxyconfiguration.go │ │ │ ├── managedproxyserviceresolver.go │ │ │ └── proxy_client.go │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ ├── internalinterfaces │ │ │ └── factory_interfaces.go │ │ │ └── proxy │ │ │ ├── interface.go │ │ │ └── v1alpha1 │ │ │ ├── interface.go │ │ │ ├── managedproxyconfiguration.go │ │ │ └── managedproxyserviceresolver.go │ └── listers │ │ └── proxy │ │ └── v1alpha1 │ │ ├── expansion_generated.go │ │ ├── managedproxyconfiguration.go │ │ └── managedproxyserviceresolver.go ├── proxyagent │ └── agent │ │ ├── agent.go │ │ ├── agent_test.go │ │ └── manifests │ │ └── charts │ │ └── addon-agent │ │ ├── Chart.yaml │ │ ├── templates │ │ ├── addon-agent-deployment.yaml │ │ ├── addon-agent-role.yaml │ │ ├── addon-agent-rolebinding.yaml │ │ ├── agent-client-secret.yaml │ │ ├── ca-secret.yaml │ │ ├── cluster-service.yaml │ │ ├── namespace.yaml │ │ ├── service-account.yaml │ │ └── services-to-expose.yaml │ │ └── values.yaml ├── proxyserver │ ├── controllers │ │ ├── ensure_secret_test.go │ │ ├── managedproxyconfiguration_controller.go │ │ ├── manifests.go │ │ └── service_resolver_controller.go │ └── operator │ │ └── authentication │ │ └── selfsigned │ │ ├── secret.go │ │ └── self_sign_certificate.go └── util │ ├── portforward.go │ ├── secrets.go │ ├── serviceresolver.go │ ├── serviceresolver_test.go │ ├── serviceurl.go │ └── serviceurl_test.go └── test ├── e2e ├── certificate │ └── certificate.go ├── connect │ └── connect.go ├── e2e.go ├── e2e_test.go ├── framework │ ├── context.go │ ├── framework.go │ └── scheme.go └── install │ ├── e2e_test.go │ └── install.go └── integration ├── controllers ├── managedproxyconfiguration_controller_test.go ├── serviceresolver_controller_test.go └── suite_test.go └── doc.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: off # disable patch status, https://docs.codecov.com/docs/commit-status#patch-status 4 | project: 5 | default: 6 | target: auto 7 | threshold: 1% 8 | 9 | ignore: 10 | - "**/*generated*.go" 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | -------------------------------------------------------------------------------- /.github/workflows/chart-upload.yml: -------------------------------------------------------------------------------- 1 | name: ChartUpload 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | env: 11 | # Common versions 12 | GO_VERSION: '1.22' 13 | GO_REQUIRED_MIN_VERSION: '' 14 | GITHUB_REF: ${{ github.ref }} 15 | CHART_NAME: 'cluster-proxy' 16 | 17 | jobs: 18 | env: 19 | name: prepare release env 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: checkout code 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 1 26 | - name: get release version 27 | run: | 28 | echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 29 | - name: get major release version 30 | run: | 31 | echo "MAJOR_RELEASE_VERSION=${RELEASE_VERSION%.*}" >> $GITHUB_ENV 32 | echo "TRIMED_RELEASE_VERSION=${RELEASE_VERSION#v}" >> $GITHUB_ENV 33 | - name: verify chart version 34 | run: | 35 | cat ./charts/cluster-proxy/Chart.yaml | grep -q 'version: ${{ env.TRIMED_RELEASE_VERSION }}' 36 | outputs: 37 | MAJOR_RELEASE_VERSION: ${{ env.MAJOR_RELEASE_VERSION }} 38 | RELEASE_VERSION: ${{ env.RELEASE_VERSION }} 39 | TRIMED_RELEASE_VERSION: ${{ env.TRIMED_RELEASE_VERSION }} 40 | upload: 41 | name: upload 42 | runs-on: ubuntu-latest 43 | needs: [ env ] 44 | permissions: 45 | contents: write 46 | steps: 47 | - name: submit charts to OCM chart repo 48 | uses: actions/github-script@v7 49 | with: 50 | github-token: ${{ secrets.OCM_BOT_PAT }} 51 | script: | 52 | try { 53 | const result = await github.rest.actions.createWorkflowDispatch({ 54 | owner: 'open-cluster-management-io', 55 | repo: 'helm-charts', 56 | workflow_id: 'download-chart.yml', 57 | ref: 'main', 58 | inputs: { 59 | repo: "${{ github.repository }}", 60 | version: "${{ needs.env.outputs.TRIMED_RELEASE_VERSION }}", 61 | "chart-name": "${{ env.CHART_NAME }}", 62 | }, 63 | }) 64 | console.log(result); 65 | } catch(error) { 66 | console.error(error); 67 | core.setFailed(error); 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/dco.yml: -------------------------------------------------------------------------------- 1 | name: DCO 2 | on: 3 | workflow_dispatch: {} 4 | pull_request: 5 | branches: 6 | - main 7 | - release-* 8 | 9 | jobs: 10 | dco_check: 11 | runs-on: ubuntu-latest 12 | name: DCO Check 13 | steps: 14 | - name: Get PR Commits 15 | id: 'get-pr-commits' 16 | uses: tim-actions/get-pr-commits@master 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | - name: DCO Check 20 | uses: tim-actions/dco@master 21 | with: 22 | commits: ${{ steps.get-pr-commits.outputs.commits }} 23 | -------------------------------------------------------------------------------- /.github/workflows/go-postsubmit.yml: -------------------------------------------------------------------------------- 1 | name: GoPostSubmit 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | workflow_dispatch: {} 9 | 10 | env: 11 | # Common versions 12 | GO_VERSION: "1.23" 13 | GO_REQUIRED_MIN_VERSION: "" 14 | 15 | jobs: 16 | images: 17 | name: images 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | arch: [amd64, arm64] 22 | steps: 23 | - name: checkout code 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 1 27 | - name: install Go 28 | uses: actions/setup-go@v2 29 | with: 30 | go-version: ${{ env.GO_VERSION }} 31 | - name: install imagebuilder 32 | run: go install github.com/openshift/imagebuilder/cmd/imagebuilder@v1.2.3 33 | - name: pull base image 34 | run: docker pull registry.access.redhat.com/ubi8/ubi-minimal:latest --platform=linux/${{ matrix.arch }} 35 | - name: images 36 | run: | 37 | IMAGE_TAG=latest-${{ matrix.arch }} \ 38 | IMAGE_BUILD_EXTRA_FLAGS="--build-arg OS=linux --build-arg ARCH=${{ matrix.arch }}" \ 39 | make images 40 | - name: push 41 | run: | 42 | echo ${{ secrets.DOCKER_PASSWORD }} | docker login quay.io --username ${{ secrets.DOCKER_USER }} --password-stdin 43 | docker push quay.io/open-cluster-management/cluster-proxy:latest-${{ matrix.arch }} 44 | image-manifest: 45 | name: image manifest 46 | runs-on: ubuntu-latest 47 | needs: [images] 48 | steps: 49 | - name: checkout code 50 | uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 1 53 | - name: create 54 | run: | 55 | echo ${{ secrets.DOCKER_PASSWORD }} | docker login quay.io --username ${{ secrets.DOCKER_USER }} --password-stdin 56 | docker manifest create quay.io/open-cluster-management/cluster-proxy:latest \ 57 | quay.io/open-cluster-management/cluster-proxy:latest-amd64 \ 58 | quay.io/open-cluster-management/cluster-proxy:latest-arm64 59 | - name: annotate 60 | run: | 61 | docker manifest annotate quay.io/open-cluster-management/cluster-proxy:latest \ 62 | quay.io/open-cluster-management/cluster-proxy:latest-amd64 --arch amd64 63 | docker manifest annotate quay.io/open-cluster-management/cluster-proxy:latest \ 64 | quay.io/open-cluster-management/cluster-proxy:latest-arm64 --arch arm64 65 | - name: push 66 | run: | 67 | docker manifest push quay.io/open-cluster-management/cluster-proxy:latest 68 | -------------------------------------------------------------------------------- /.github/workflows/go-presubmit.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - release-* 9 | pull_request: 10 | branches: 11 | - main 12 | - release-* 13 | 14 | env: 15 | # Common versions 16 | GO_VERSION: "1.23" 17 | GO_REQUIRED_MIN_VERSION: "" 18 | 19 | jobs: 20 | build: 21 | name: build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: checkout code 25 | uses: actions/checkout@v4 26 | - name: install Go 27 | uses: actions/setup-go@v2 28 | with: 29 | go-version: ${{ env.GO_VERSION }} 30 | - name: build 31 | run: make build 32 | linelint: 33 | name: linelint 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | - name: Linelint 39 | uses: fernandrone/linelint@0.0.4 40 | verify: 41 | name: verify 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v4 46 | - name: install Go 47 | uses: actions/setup-go@v2 48 | with: 49 | go-version: ${{ env.GO_VERSION }} 50 | - name: verify 51 | run: make verify 52 | unit: 53 | name: unit 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: checkout code 57 | uses: actions/checkout@v4 58 | - name: install Go 59 | uses: actions/setup-go@v2 60 | with: 61 | go-version: ${{ env.GO_VERSION }} 62 | - name: unit 63 | run: make test 64 | - name: report coverage 65 | uses: codecov/codecov-action@v4 66 | with: 67 | token: ${{ secrets.CODECOV_UPLOAD_TOKEN }} 68 | files: ./cover.out 69 | flags: unit 70 | name: unit 71 | verbose: true 72 | fail_ci_if_error: true 73 | 74 | integration: 75 | name: integration 76 | runs-on: ubuntu-latest 77 | steps: 78 | - name: checkout code 79 | uses: actions/checkout@v4 80 | - name: install Go 81 | uses: actions/setup-go@v2 82 | with: 83 | go-version: ${{ env.GO_VERSION }} 84 | - name: integration 85 | run: make test-integration 86 | 87 | e2e: 88 | name: e2e 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: checkout code 92 | uses: actions/checkout@v4 93 | - name: install Go 94 | uses: actions/setup-go@v2 95 | with: 96 | go-version: ${{ env.GO_VERSION }} 97 | - name: Install clusteradm 98 | run: curl -L https://raw.githubusercontent.com/open-cluster-management-io/clusteradm/main/install.sh | bash 99 | - name: Create k8s Kind Cluster 100 | uses: helm/kind-action@v1.2.0 101 | - name: Prepare OCM testing environment 102 | run: | 103 | clusteradm init --output-join-command-file join.sh --wait 104 | sh -c "$(cat join.sh) loopback --force-internal-endpoint-lookup" 105 | clusteradm accept --clusters loopback --wait 30 106 | kubectl wait --for=condition=ManagedClusterConditionAvailable managedcluster/loopback 107 | - name: Build image 108 | run: | 109 | make images 110 | kind load docker-image quay.io/open-cluster-management/cluster-proxy:latest --name chart-testing 111 | - name: Install latest cluster-proxy 112 | run: | 113 | helm install \ 114 | -n open-cluster-management-addon --create-namespace \ 115 | cluster-proxy charts/cluster-proxy/ \ 116 | --set tag=latest --set installByPlacement.placementName=default 117 | - name: Build&Run e2e test 118 | run: | 119 | kubectl wait --for=condition=ProxyServerDeployed=true managedproxyconfiguration cluster-proxy --timeout=60s 120 | kubectl wait --for=condition=Available deployment/cluster-proxy --timeout=60s -n open-cluster-management-addon 121 | kubectl port-forward -n open-cluster-management-addon services/proxy-entrypoint 8090:8090 & 122 | make test-e2e 123 | -------------------------------------------------------------------------------- /.github/workflows/go-release.yml: -------------------------------------------------------------------------------- 1 | name: GoRelease 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | env: 8 | # Common versions 9 | GO_VERSION: "1.23" 10 | GO_REQUIRED_MIN_VERSION: "" 11 | GITHUB_REF: ${{ github.ref }} 12 | CHART_NAME: "cluster-proxy" 13 | 14 | jobs: 15 | env: 16 | name: prepare release env 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 1 23 | - name: get release version 24 | run: | 25 | echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 26 | - name: get major release version 27 | run: | 28 | echo "MAJOR_RELEASE_VERSION=${RELEASE_VERSION%.*}" >> $GITHUB_ENV 29 | echo "TRIMED_RELEASE_VERSION=${RELEASE_VERSION#v}" >> $GITHUB_ENV 30 | - name: verify chart version 31 | run: | 32 | cat ./charts/cluster-proxy/Chart.yaml | grep -q 'version: ${{ env.TRIMED_RELEASE_VERSION }}' 33 | outputs: 34 | MAJOR_RELEASE_VERSION: ${{ env.MAJOR_RELEASE_VERSION }} 35 | RELEASE_VERSION: ${{ env.RELEASE_VERSION }} 36 | TRIMED_RELEASE_VERSION: ${{ env.TRIMED_RELEASE_VERSION }} 37 | images: 38 | name: images 39 | runs-on: ubuntu-latest 40 | needs: [env] 41 | strategy: 42 | matrix: 43 | arch: [amd64, arm64] 44 | steps: 45 | - name: checkout code 46 | uses: actions/checkout@v4 47 | with: 48 | fetch-depth: 1 49 | - name: install Go 50 | uses: actions/setup-go@v2 51 | with: 52 | go-version: ${{ env.GO_VERSION }} 53 | - name: install imagebuilder 54 | run: go install github.com/openshift/imagebuilder/cmd/imagebuilder@v1.2.3 55 | - name: pull base image 56 | run: docker pull registry.access.redhat.com/ubi8/ubi-minimal:latest --platform=linux/${{ matrix.arch }} 57 | - name: images 58 | run: | 59 | IMAGE_TAG=${{ needs.env.outputs.RELEASE_VERSION }}-${{ matrix.arch }} \ 60 | IMAGE_BUILD_EXTRA_FLAGS="--build-arg OS=linux --build-arg ARCH=${{ matrix.arch }}" \ 61 | make images 62 | - name: push 63 | run: | 64 | echo ${{ secrets.DOCKER_PASSWORD }} | docker login quay.io --username ${{ secrets.DOCKER_USER }} --password-stdin 65 | docker push quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }}-${{ matrix.arch }} 66 | image-manifest: 67 | name: image manifest 68 | runs-on: ubuntu-latest 69 | needs: [env, images] 70 | steps: 71 | - name: checkout code 72 | uses: actions/checkout@v4 73 | with: 74 | fetch-depth: 1 75 | - name: create 76 | run: | 77 | echo ${{ secrets.DOCKER_PASSWORD }} | docker login quay.io --username ${{ secrets.DOCKER_USER }} --password-stdin 78 | docker manifest create quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }} \ 79 | quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }}-amd64 \ 80 | quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }}-arm64 81 | - name: annotate 82 | run: | 83 | docker manifest annotate quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }} \ 84 | quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }}-amd64 --arch amd64 85 | docker manifest annotate quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }} \ 86 | quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }}-arm64 --arch arm64 87 | - name: push 88 | run: | 89 | docker manifest push quay.io/open-cluster-management/cluster-proxy:${{ needs.env.outputs.RELEASE_VERSION }} 90 | release: 91 | name: release 92 | runs-on: ubuntu-latest 93 | needs: [env, image-manifest] 94 | steps: 95 | - name: checkout code 96 | uses: actions/checkout@v4 97 | with: 98 | fetch-depth: 1 99 | - name: setup helm 100 | uses: azure/setup-helm@v1 101 | - name: chart package 102 | run: | 103 | mkdir -p release 104 | pushd release 105 | helm package ../charts/${{ env.CHART_NAME }}/ 106 | popd 107 | - name: generate changelog 108 | run: | 109 | echo "# Cluster Proxy ${{ needs.env.outputs.RELEASE_VERSION }}" > /home/runner/work/changelog.txt 110 | - name: publish release 111 | uses: softprops/action-gh-release@v1 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | with: 115 | body_path: /home/runner/work/changelog.txt 116 | files: | 117 | release/*.tgz 118 | draft: true 119 | prerelease: false 120 | generate_release_notes: true 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | /apiserver.local.config 18 | /bin 19 | /default.etcd 20 | /kubeconfig 21 | /config/crds/ 22 | .idea/** 23 | .DS_Store 24 | 25 | index.yaml 26 | cluster-proxy-*.tgz 27 | 28 | /testbin 29 | 30 | join.sh 31 | 32 | vendor/* 33 | -------------------------------------------------------------------------------- /.linelint.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | end-of-file: 3 | enable: true 4 | ignore: 5 | - FQA.md 6 | - hack/boilerplate.go.txt 7 | -------------------------------------------------------------------------------- /CHANGELOG/CHANGELOG-0.3.0.md: -------------------------------------------------------------------------------- 1 | # Changelog since v0.2.2 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## v0.3.0 5 | 6 | ### New Features 7 | * Add ServiceResolvers to expose services from managed clusters to a hub.([#128](https://github.com/open-cluster-management-io/cluster-proxy/pull/128)) 8 | 9 | ### Added 10 | * Support AddonDeploymentConfig([#139](https://github.com/open-cluster-management-io/cluster-proxy/pull/139)) 11 | 12 | ### Changes 13 | N/C 14 | 15 | ### Bug Fixes 16 | N/C 17 | 18 | ### Removed & Deprecated 19 | N/C 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Refer to our [Open Cluster Management Community Code of Conduct](https://github.com/open-cluster-management/community/blob/main/CODE_OF_CONDUCT.md) 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | **Table of Contents** 2 | 3 | - [Contributing guidelines](#contributing-guidelines) 4 | - [Terms](#terms) 5 | - [Certificate of Origin](#certificate-of-origin) 6 | - [DCO Sign Off](#dco-sign-off) 7 | - [Code of Conduct](#code-of-conduct) 8 | - [Contributing a patch](#contributing-a-patch) 9 | - [Issue and pull request management](#issue-and-pull-request-management) 10 | - [Pre-check before submitting a PR](#pre-check-before-submitting-a-pr) 11 | 12 | # Contributing guidelines 13 | 14 | ## Terms 15 | 16 | All contributions to the repository must be submitted under the terms of the [Apache Public License 2.0](https://www.apache.org/licenses/LICENSE-2.0). 17 | 18 | ## Certificate of Origin 19 | 20 | By contributing to this project, you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the contribution. See the [DCO](https://github.com/open-cluster-management/community/blob/main/DCO) file for details. 21 | 22 | ## DCO Sign Off 23 | 24 | You must sign off your commit to state that you certify the [DCO](https://github.com/open-cluster-management/community/blob/main/DCO). To certify your commit for DCO, add a line like the following at the end of your commit message: 25 | 26 | ``` 27 | Signed-off-by: John Smith 28 | ``` 29 | 30 | This can be done with the `--signoff` option to `git commit`. See the [Git documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) for details. 31 | 32 | ## Code of Conduct 33 | 34 | The Open Cluster Management project has adopted the CNCF Code of Conduct. Refer to our [Community Code of Conduct](https://github.com/open-cluster-management/community/blob/main/CODE_OF_CONDUCT.md) for details. 35 | 36 | ## Contributing a patch 37 | 38 | 1. Submit an issue describing your proposed change to the repository in question. The repository owners will respond to your issue promptly. 39 | 2. Fork the desired repository, then develop and test your code changes. 40 | 3. Submit a pull request. 41 | 42 | ## Issue and pull request management 43 | 44 | Anyone can comment on issues and submit reviews for pull requests. In order to be assigned an issue or pull request, you can leave a `/assign ` comment on the issue or pull request (PR). 45 | 46 | ## Pre-check before submitting a PR 47 | 48 | 49 | Before submitting a PR, please perform the following steps: 50 | 51 | - List of steps to perform before submitting a PR. 52 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /FQA.md: -------------------------------------------------------------------------------- 1 | # FQA 2 | 3 | 4 | 5 | ## 1. What is drawback of "PortForward" mode proxy ingress? 6 | 7 | The "PortForward" will be starting a local proxy server in the addon agent 8 | which is proxying the tunnel handshake and data via a port-forward long 9 | connection. E.g. for a 3-replicas proxy server and 3-replicas proxy agent 10 | environment, each agent will be maintaining 3 port-forward connection so 11 | in all there's will be 3x3=9 long connections from each managed clusters. 12 | The kube-apiserver has a limit in HTTP2 max concurrent streams prescribed 13 | by `--http2-max-streams-per-connection` flag which is defaulted to 1000. 14 | So under "PortForward" mode we need to take care of the number of inflight 15 | long-running streams when we're managing more and more clusters. 16 | 17 | ## 2. What if my hub cluster doesn't support "LoadBalancer" type service? 18 | 19 | By default, the cluster-proxy addon-manager will be automatically provisioning 20 | a "LoadBalancer" typed service which is for listening tunnel handshakes from the 21 | managed clusters. As a workaround, we can explicitly set an address url for the 22 | proxy agents so that they know where to initiate the registration upon starting: 23 | 24 | - For helm chart installation, adding a `--set-string 25 | proxyServer.entrypointAddress=` flag to 26 | prescribe the registration entry for the proxy agents. 27 | - For opt-in to custom proxy server hostname for existing installation, editing 28 | the "ManagedProxyConfiguration" custom resource by: 29 | 30 | > $ kubectl edit managedproxyconfiguration cluster-proxy 31 | 32 | Then replace the entrypoint type from "LoadBalancerService" to "Hostname": 33 | 34 | ```yaml 35 | spec: 36 | ... 37 | proxyServer: 38 | entrypoint: 39 | hostname: 40 | value: 41 | type: Hostname 42 | ``` 43 | 44 | Note that the custom hostname will be automatically signed into proxy servers' 45 | server-side X509 certificate upon changes and the hostname address shall be 46 | __accessible__ from each of the managed clusters. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Image URL to use all building/pushing image targets 2 | IMG ?= controller:latest 3 | IMAGE_REGISTRY_NAME ?= quay.io/open-cluster-management 4 | IMAGE_NAME = cluster-proxy 5 | IMAGE_TAG ?= latest 6 | E2E_TEST_CLUSTER_NAME ?= loopback 7 | CONTAINER_ENGINE ?= docker 8 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 9 | CRD_OPTIONS ?= "crd:crdVersions={v1},allowDangerousTypes=true,generateEmbeddedObjectMeta=true" 10 | 11 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 12 | ifeq (,$(shell go env GOBIN)) 13 | GOBIN=$(shell go env GOPATH)/bin 14 | else 15 | GOBIN=$(shell go env GOBIN) 16 | endif 17 | 18 | # Setting SHELL to bash allows bash commands to be executed by recipes. 19 | # This is a requirement for 'setup-envtest.sh' in the test target. 20 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 21 | SHELL = /usr/bin/env bash -o pipefail 22 | .SHELLFLAGS = -ec 23 | 24 | all: build 25 | 26 | ##@ General 27 | 28 | # The help target prints out all targets with their descriptions organized 29 | # beneath their categories. The categories are represented by '##@' and the 30 | # target descriptions by '##'. The awk commands is responsible for reading the 31 | # entire set of makefiles included in this invocation, looking for lines of the 32 | # file as xyz: ## something, and then pretty-format the target and help. Then, 33 | # if there's a line with ##@ something, that gets pretty-printed as a category. 34 | # More info on the usage of ANSI control characters for terminal formatting: 35 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 36 | # More info on the awk command: 37 | # http://linuxcommand.org/lc3_adv_awk.php 38 | 39 | help: ## Display this help. 40 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 41 | 42 | ##@ Development 43 | 44 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 45 | $(CONTROLLER_GEN) $(CRD_OPTIONS) \ 46 | paths="./pkg/apis/..." \ 47 | rbac:roleName=manager-role \ 48 | output:crd:artifacts:config=hack/crd/bases 49 | 50 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 51 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./pkg/apis/..." 52 | 53 | fmt: ## Run go fmt against code. 54 | go fmt ./... 55 | 56 | vet: ## Run go vet against code. 57 | go vet ./... 58 | 59 | golint: 60 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 61 | golangci-lint run --timeout=3m ./... 62 | 63 | verify: fmt vet golint 64 | 65 | test: manifests generate fmt vet ## Run tests. 66 | go test ./pkg/... -coverprofile cover.out 67 | 68 | ##@ Build 69 | 70 | build: generate fmt vet 71 | go build -o bin/addon-manager cmd/addon-manager/main.go 72 | go build -o bin/addon-agent cmd/addon-agent/main.go 73 | 74 | docker-build: test ## Build docker image with the manager. 75 | $(CONTAINER_ENGINE) build -t ${IMG} . 76 | 77 | docker-push: ## Push docker image with the manager. 78 | $(CONTAINER_ENGINE) push ${IMG} 79 | 80 | ##@ Deployment 81 | 82 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 83 | controller-gen: ## Download controller-gen locally if necessary. 84 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.15.0) 85 | 86 | KUSTOMIZE = $(shell pwd)/bin/kustomize 87 | kustomize: ## Download kustomize locally if necessary. 88 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 89 | 90 | # go-get-tool will 'go get' any package $2 and install it to $1. 91 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 92 | define go-get-tool 93 | @[ -f $(1) ] || { \ 94 | set -e ;\ 95 | TMP_DIR=$$(mktemp -d) ;\ 96 | cd $$TMP_DIR ;\ 97 | go mod init tmp ;\ 98 | echo "Downloading $(2)" ;\ 99 | GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ 100 | rm -rf $$TMP_DIR ;\ 101 | } 102 | endef 103 | 104 | client-gen: 105 | go install k8s.io/code-generator/cmd/client-gen@v0.29.2 106 | go install sigs.k8s.io/apiserver-runtime/tools/apiserver-runtime-gen@v1.1.1 107 | apiserver-runtime-gen \ 108 | --module open-cluster-management.io/cluster-proxy \ 109 | -g client-gen \ 110 | -g informer-gen \ 111 | -g lister-gen \ 112 | --versions=open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1 113 | 114 | images: 115 | $(CONTAINER_ENGINE) build \ 116 | -f cmd/Dockerfile \ 117 | --build-arg ADDON_AGENT_IMAGE_NAME=$(IMAGE_REGISTRY_NAME)/$(IMAGE_NAME):$(IMAGE_TAG) \ 118 | -t $(IMAGE_REGISTRY_NAME)/$(IMAGE_NAME):$(IMAGE_TAG) . 119 | 120 | ENVTEST_ASSETS_DIR=$(shell pwd)/testbin 121 | test-integration: manifests generate fmt vet 122 | mkdir -p ${ENVTEST_ASSETS_DIR} 123 | test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.8.3/hack/setup-envtest.sh 124 | source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; \ 125 | fetch_envtest_tools $(ENVTEST_ASSETS_DIR); \ 126 | setup_envtest_env $(ENVTEST_ASSETS_DIR); \ 127 | go test ./test/integration/... -coverprofile cover.out 128 | 129 | e2e-job-image: 130 | $(CONTAINER_ENGINE) build \ 131 | -f test/e2e/job/Dockerfile \ 132 | -t $(IMAGE_REGISTRY_NAME)/$(IMAGE_NAME)-e2e-job:$(IMAGE_TAG) . 133 | 134 | build-e2e: 135 | go test -c -o bin/e2e ./test/e2e/ 136 | 137 | test-e2e: build-e2e 138 | ./bin/e2e --test-cluster $(E2E_TEST_CLUSTER_NAME) 139 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - qiujian16 3 | - yue9944882 4 | - xuezhaojun 5 | 6 | reviewers: 7 | - qiujian16 8 | - yue9944882 9 | - xuezhaojun 10 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: open-cluster-management.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | projectName: cluster-proxy 5 | repo: open-cluster-management.io/cluster-proxy 6 | resources: 7 | - api: 8 | crdVersion: v1 9 | controller: true 10 | domain: open-cluster-management.io 11 | group: proxy 12 | kind: ManagedProxyConfiguration 13 | path: open-cluster-management.io/cluster-proxy/api/v1alpha1 14 | version: v1alpha1 15 | version: "3" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cluster Proxy 2 | 3 | [![License](https://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![Go](https://github.com/open-cluster-management-io/cluster-proxy/actions/workflows/go-presubmit.yml/badge.svg)](https://github.com/open-cluster-management-io/cluster-proxy/actions/workflows/go-presubmit.yml) 5 | 6 | 7 | ## What is Cluster Proxy? 8 | 9 | Cluster Proxy is a pluggable addon working on OCM rebased on the extensibility 10 | provided by [addon-framework](https://github.com/open-cluster-management-io/addon-framework) 11 | which automates the installation of [apiserver-network-proxy](https://github.com/kubernetes-sigs/apiserver-network-proxy) 12 | on both hub cluster and managed clusters. The network proxy will be establishing 13 | reverse proxy tunnels from the managed cluster to the hub cluster to make the 14 | clients from the hub network can access the services in the managed clusters' 15 | network even if all the clusters are isolated in different VPCs. 16 | 17 | Cluster Proxy consists of two components: 18 | 19 | - __Addon-Manager__: Manages the installation of proxy-servers i.e. proxy ingress 20 | in the hub cluster. 21 | 22 | - __Addon-Agent__: Manages the installation of proxy-agents for each managed 23 | clusters. 24 | 25 | The overall architecture is shown below: 26 | 27 | ![Arch](./hack/picture/arch.png) 28 | 29 | 30 | ## Getting started 31 | 32 | ### Prerequisite 33 | 34 | - OCM registration (>= 0.5.0) 35 | 36 | ### Steps 37 | 38 | #### Installing via Helm Chart 39 | 40 | 1. Adding helm repo: 41 | 42 | ```shell 43 | $ helm repo add ocm https://openclustermanagement.blob.core.windows.net/releases/ 44 | $ helm repo update 45 | $ helm search repo ocm/cluster-proxy 46 | NAME CHART VERSION APP VERSION DESCRIPTION 47 | ocm/cluster-proxy <..> 1.0.0 A Helm chart for Cluster-Proxy 48 | ``` 49 | 50 | 2. Install the helm chart: 51 | 52 | ```shell 53 | $ helm install \ 54 | -n open-cluster-management-addon --create-namespace \ 55 | cluster-proxy ocm/cluster-proxy 56 | $ kubectl -n open-cluster-management-cluster-proxy get pod 57 | NAME READY STATUS RESTARTS AGE 58 | cluster-proxy-5d8db7ddf4-265tm 1/1 Running 0 12s 59 | cluster-proxy-addon-manager-778f6d679f-9pndv 1/1 Running 0 33s 60 | ... 61 | ``` 62 | 63 | 3. The addon will be automatically installed to your registered clusters, 64 | verify the addon installation: 65 | 66 | ```shell 67 | $ kubectl get managedclusteraddon -A | grep cluster-proxy 68 | NAMESPACE NAME AVAILABLE DEGRADED PROGRESSING 69 | cluster-proxy True 70 | ``` 71 | 72 | ### Usage 73 | 74 | By default, the proxy servers are running in GPRC mode so the proxy clients 75 | are expected to proxy through the tunnels by the [konnectivity-client](https://github.com/kubernetes-sigs/apiserver-network-proxy#clients). 76 | Konnectivity is the underlying technique of Kubernetes' [egress-selector](https://kubernetes.io/docs/tasks/extend-kubernetes/setup-konnectivity/) 77 | feature and an example of konnectivity client is visible [here](https://github.com/open-cluster-management-io/cluster-proxy/tree/main/examples/test-client). 78 | 79 | Codewisely proxying to the managed cluster will be simply overriding the 80 | dialer of the kubernetes original client config object, e.g.: 81 | 82 | ```go 83 | // instantiate a gprc proxy dialer 84 | tunnel, err := konnectivity.CreateSingleUseGrpcTunnel( 85 | context.TODO(), 86 | , 87 | grpc.WithTransportCredentials(grpccredentials.NewTLS(proxyTLSCfg)), 88 | ) 89 | cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 90 | if err != nil { 91 | return err 92 | } 93 | // The managed cluster's name. 94 | cfg.Host = clusterName 95 | // Override the default tcp dialer 96 | cfg.Dial = tunnel.DialContext 97 | ``` 98 | 99 | ### Performance 100 | 101 | Here's the result of network bandwidth benchmarking via [goben](https://github.com/udhos/goben) 102 | with or without Cluster-Proxy (i.e. Apiserver-Network-Proxy) so roughly the proxying 103 | through the tunnel will involve 1/2 performance loss so it's recommended to avoid 104 | transferring data-intensive traffic over the proxy. 105 | 106 | 107 | | Bandwidth | Direct | over Cluster-Proxy | 108 | |-------------|------------|--------------------| 109 | | Read/Mbps | 902 Mbps | 461 Mbps | 110 | | Write/Mbps | 889 Mbps | 428 Mbps | 111 | 112 | 113 | 114 | ## References 115 | 116 | - Design: [https://github.com/open-cluster-management-io/enhancements/tree/main/enhancements/sig-architecture/14-addon-cluster-proxy](https://github.com/open-cluster-management-io/enhancements/tree/main/enhancements/sig-architecture/14-addon-cluster-proxy) 117 | - Addon-Framework: [https://github.com/open-cluster-management-io/addon-framework](https://github.com/open-cluster-management-io/addon-framework) 118 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Refer to our [Community Security Response](https://github.com/open-cluster-management-io/community/blob/main/SECURITY.md). 2 | -------------------------------------------------------------------------------- /charts/cluster-proxy/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cluster-proxy 3 | description: A Helm chart for Cluster-Proxy OCM Addon 4 | type: application 5 | version: 0.7.0 6 | appVersion: 1.0.0 7 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/clustermanagementaddon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: addon.open-cluster-management.io/v1alpha1 2 | kind: ClusterManagementAddOn 3 | metadata: 4 | name: cluster-proxy 5 | annotations: 6 | "addon.open-cluster-management.io/lifecycle": "addon-manager" 7 | spec: 8 | addOnMeta: 9 | displayName: cluster-proxy 10 | description: cluster-proxy 11 | supportedConfigs: 12 | - group: proxy.open-cluster-management.io 13 | resource: managedproxyconfigurations 14 | defaultConfig: 15 | name: cluster-proxy 16 | - group: addon.open-cluster-management.io 17 | resource: addondeploymentconfigs 18 | installStrategy: 19 | type: Placements 20 | placements: 21 | - name: {{ .Values.installByPlacement.placementName | default "cluster-proxy-placement" }} 22 | namespace: {{ .Values.installByPlacement.placementNamespace | default .Release.Namespace }} 23 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: open-cluster-management:cluster-proxy:addon-manager 5 | rules: 6 | - apiGroups: 7 | - cluster.open-cluster-management.io 8 | resources: 9 | - managedclusters 10 | - managedclustersets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - addon.open-cluster-management.io 17 | resources: 18 | - clustermanagementaddons 19 | - managedclusteraddons 20 | - clustermanagementaddons/status 21 | - clustermanagementaddons/finalizers 22 | - managedclusteraddons/status 23 | verbs: 24 | - '*' 25 | - apiGroups: 26 | - addon.open-cluster-management.io 27 | resources: 28 | - addondeploymentconfigs 29 | verbs: 30 | - get 31 | - list 32 | - watch 33 | - apiGroups: 34 | - addon.open-cluster-management.io 35 | resources: 36 | - managedclusteraddons/finalizers 37 | verbs: 38 | - '*' 39 | - apiGroups: 40 | - proxy.open-cluster-management.io 41 | resources: 42 | - managedproxyconfigurations 43 | - managedproxyconfigurations/status 44 | - managedproxyconfigurations/finalizers 45 | - managedproxyserviceresolvers 46 | - managedproxyserviceresolvers/status 47 | - managedproxyserviceresolvers/finalizers 48 | verbs: 49 | - '*' 50 | - apiGroups: 51 | - certificates.k8s.io 52 | resources: 53 | - certificatesigningrequests 54 | - certificatesigningrequests/approval 55 | - certificatesigningrequests/status 56 | verbs: 57 | - get 58 | - list 59 | - watch 60 | - update 61 | - patch 62 | - apiGroups: 63 | - certificates.k8s.io 64 | resources: 65 | - signers 66 | verbs: 67 | - "*" 68 | resourceNames: 69 | - open-cluster-management.io/proxy-agent-signer 70 | - kubernetes.io/kube-apiserver-client 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - namespaces 75 | - secrets 76 | - pods 77 | - pods/portforward 78 | verbs: 79 | - "*" 80 | - apiGroups: 81 | - "" 82 | resources: 83 | - serviceaccounts 84 | - services 85 | verbs: 86 | - get 87 | - list 88 | - watch 89 | - apiGroups: 90 | - apps 91 | resources: 92 | - deployments 93 | verbs: 94 | - get 95 | - list 96 | - watch 97 | - apiGroups: 98 | - rbac.authorization.k8s.io 99 | resources: 100 | - roles 101 | - rolebindings 102 | verbs: 103 | - get 104 | - list 105 | - watch 106 | - create 107 | - update 108 | - patch 109 | - apiGroups: 110 | - work.open-cluster-management.io 111 | resources: 112 | - manifestworks 113 | verbs: 114 | - get 115 | - list 116 | - watch 117 | - create 118 | - update 119 | - patch 120 | - apiGroups: 121 | - coordination.k8s.io 122 | resources: 123 | - leases 124 | verbs: 125 | - "*" 126 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: open-cluster-management:cluster-proxy:addon-manager 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: open-cluster-management:cluster-proxy:addon-manager 9 | subjects: 10 | - kind: ServiceAccount 11 | name: cluster-proxy 12 | namespace: {{ .Release.Namespace }} 13 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/clustersetbinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if not .Values.installByPlacement.placementName }} 2 | 3 | apiVersion: cluster.open-cluster-management.io/v1beta2 4 | kind: ManagedClusterSetBinding 5 | metadata: 6 | name: global 7 | namespace: {{ .Release.Namespace }} 8 | spec: 9 | clusterSet: global 10 | 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/managedproxyconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: proxy.open-cluster-management.io/v1alpha1 2 | kind: ManagedProxyConfiguration 3 | metadata: 4 | name: cluster-proxy 5 | spec: 6 | authentication: 7 | dump: 8 | secrets: {} 9 | signer: 10 | type: SelfSigned 11 | proxyServer: 12 | image: {{ .Values.proxyServerImage }}:{{ .Values.tag | default (print "v" .Chart.Version) }} 13 | replicas: {{ .Values.replicas }} 14 | namespace: {{ .Release.Namespace }} 15 | entrypoint: 16 | {{- if .Values.proxyServer.entrypointAddress }} 17 | type: Hostname 18 | hostname: 19 | value: {{ .Values.proxyServer.entrypointAddress }} 20 | {{- else if .Values.proxyServer.entrypointLoadBalancer }} 21 | type: LoadBalancerService 22 | loadBalancerService: {} 23 | {{- else }} 24 | type: PortForward 25 | {{- end }} 26 | port: {{ .Values.proxyServer.port }} 27 | proxyAgent: 28 | image: {{ .Values.proxyAgentImage }}:{{ .Values.tag | default (print "v" .Chart.Version) }} 29 | replicas: {{ .Values.replicas }} 30 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/manager-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: cluster-proxy-addon-manager 6 | spec: 7 | replicas: {{ .Values.replicas }} 8 | selector: 9 | matchLabels: 10 | open-cluster-management.io/addon: cluster-proxy 11 | template: 12 | metadata: 13 | labels: 14 | open-cluster-management.io/addon: cluster-proxy 15 | spec: 16 | serviceAccount: cluster-proxy 17 | containers: 18 | - name: manager 19 | image: {{ .Values.registry }}/{{ .Values.image }}:{{ .Values.tag | default (print "v" .Chart.Version) }} 20 | imagePullPolicy: IfNotPresent 21 | command: 22 | - /manager 23 | args: 24 | - --leader-elect=true 25 | - --signer-secret-namespace={{ .Release.Namespace }} 26 | securityContext: 27 | allowPrivilegeEscalation: false 28 | capabilities: 29 | drop: 30 | - ALL 31 | privileged: false 32 | runAsNonRoot: true 33 | readOnlyRootFilesystem: true 34 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/placement.yaml: -------------------------------------------------------------------------------- 1 | {{- if not .Values.installByPlacement.placementName }} 2 | apiVersion: cluster.open-cluster-management.io/v1beta1 3 | kind: Placement 4 | metadata: 5 | name: cluster-proxy-placement 6 | namespace: {{ .Release.Namespace }} 7 | spec: 8 | clusterSets: 9 | - global 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: open-cluster-management:cluster-proxy:addon-manager 5 | namespace: {{ .Release.Namespace }} 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - services 11 | - events 12 | - serviceaccounts 13 | verbs: 14 | - "*" 15 | - apiGroups: 16 | - "apps" 17 | resources: 18 | - deployments 19 | - deployments/scale 20 | verbs: 21 | - "*" 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - configmaps 26 | verbs: 27 | - get 28 | - create 29 | - update 30 | - patch 31 | - apiGroups: 32 | - coordination.k8s.io 33 | resources: 34 | - leases 35 | verbs: 36 | - get 37 | - create 38 | - update 39 | - patch 40 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: open-cluster-management:cluster-proxy:addon-manager 5 | namespace: {{ .Release.Namespace }} 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: open-cluster-management:cluster-proxy:addon-manager 10 | subjects: 11 | - kind: ServiceAccount 12 | name: cluster-proxy 13 | namespace: {{ .Release.Namespace }} 14 | -------------------------------------------------------------------------------- /charts/cluster-proxy/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: cluster-proxy 5 | namespace: {{ .Release.Namespace }} 6 | -------------------------------------------------------------------------------- /charts/cluster-proxy/values.yaml: -------------------------------------------------------------------------------- 1 | # Image registry 2 | registry: quay.io/open-cluster-management 3 | 4 | # Image of the cluster-gateway instances 5 | image: cluster-proxy 6 | 7 | # Image tag 8 | tag: 9 | 10 | # Number of replicas 11 | replicas: 1 12 | 13 | spokeAddonNamespace: "open-cluster-management-cluster-proxy" 14 | 15 | proxyServerImage: quay.io/open-cluster-management/cluster-proxy 16 | proxyAgentImage: quay.io/open-cluster-management/cluster-proxy 17 | 18 | proxyServer: 19 | entrypointLoadBalancer: false 20 | entrypointAddress: "" 21 | port: 8091 22 | 23 | installByPlacement: 24 | placementName: "" 25 | placementNamespace: "" 26 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/labels" 9 | "k8s.io/client-go/rest" 10 | clusterv1client "open-cluster-management.io/api/client/cluster/clientset/versioned" 11 | "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 12 | "open-cluster-management.io/cluster-proxy/pkg/util" 13 | clustersdkv1beta2 "open-cluster-management.io/sdk-go/pkg/apis/cluster/v1beta2" 14 | ) 15 | 16 | func GetProxyHost(ctx context.Context, kubeconfig *rest.Config, clusterName string, namespace string, serviceName string) (string, error) { 17 | client := versioned.NewForConfigOrDie(kubeconfig) 18 | mpsrList, err := client.ProxyV1alpha1().ManagedProxyServiceResolvers().List(ctx, v1.ListOptions{}) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | // Get labels of the managedCluster 24 | clusterClient, err := clusterv1client.NewForConfig(kubeconfig) 25 | if err != nil { 26 | return "", err 27 | } 28 | managedCluster, err := clusterClient.ClusterV1().ManagedClusters().Get(ctx, clusterName, v1.GetOptions{}) 29 | if err != nil { 30 | return "", err 31 | } 32 | 33 | // Return when namespace, serviceName and labels of the managedCluster are all matched 34 | for _, sr := range mpsrList.Items { 35 | if !util.IsServiceResolverLegal(&sr) { 36 | continue 37 | } 38 | 39 | set, err := clusterClient.ClusterV1beta2().ManagedClusterSets().Get(ctx, sr.Spec.ManagedClusterSelector.ManagedClusterSet.Name, v1.GetOptions{}) 40 | if err != nil { 41 | return "", err 42 | } 43 | selector, err := clustersdkv1beta2.BuildClusterSelector(set) 44 | if err != nil { 45 | return "", err 46 | } 47 | if !selector.Matches(labels.Set(managedCluster.Labels)) { 48 | continue 49 | } 50 | 51 | if sr.Spec.ServiceSelector.ServiceRef.Namespace != namespace || sr.Spec.ServiceSelector.ServiceRef.Name != serviceName { 52 | continue 53 | } 54 | 55 | return util.GenerateServiceURL(clusterName, namespace, serviceName), nil 56 | } 57 | 58 | return "", fmt.Errorf("Not found any suitable ManagedProxyServiceResolver for (cluster:%s, namespace: %s, service: %s)", clusterName, namespace, serviceName) 59 | } 60 | -------------------------------------------------------------------------------- /cmd/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | 4 | WORKDIR /workspace 5 | 6 | ARG APISERVER_NETWORK_PROXY_VERSION=0.30.2 7 | ARG KUBECTL_VERSION=v1.30.2 8 | ARG ADDON_AGENT_IMAGE_NAME 9 | 10 | # Build Apiserver-network-proxy binaries 11 | RUN wget https://github.com/kubernetes-sigs/apiserver-network-proxy/archive/refs/tags/v${APISERVER_NETWORK_PROXY_VERSION}.tar.gz \ 12 | && tar xzvf v${APISERVER_NETWORK_PROXY_VERSION}.tar.gz \ 13 | && cd apiserver-network-proxy-${APISERVER_NETWORK_PROXY_VERSION} \ 14 | && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /workspace/proxy-server ./cmd/server/ \ 15 | && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /workspace/proxy-agent ./cmd/agent/ \ 16 | && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /workspace/proxy-test-client ./cmd/test-client/ \ 17 | && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /workspace/proxy-test-server ./cmd/test-server/ \ 18 | && cd /workspace \ 19 | && curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" \ 20 | && chmod a+x kubectl 21 | 22 | # Copy the Go Modules manifests 23 | COPY go.mod go.mod 24 | COPY go.sum go.sum 25 | # cache deps before building and copying source so that we don't need to re-download as much 26 | # and so that source changes don't invalidate our downloaded layer 27 | RUN go mod download 28 | 29 | # Copy the go source 30 | COPY cmd/ cmd/ 31 | COPY pkg pkg/ 32 | 33 | # Build addons 34 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o agent cmd/addon-agent/main.go 35 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a \ 36 | -ldflags="-X 'open-cluster-management.io/cluster-proxy/pkg/config.AgentImageName=${ADDON_AGENT_IMAGE_NAME}'" \ 37 | -o manager cmd/addon-manager/main.go 38 | 39 | # Use distroless as minimal base image to package the manager binary 40 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 41 | FROM alpine:3.13 42 | 43 | WORKDIR / 44 | RUN apk add libc6-compat 45 | COPY --from=builder /workspace/kubectl /workspace/proxy-server /workspace/proxy-agent /workspace/proxy-test-client /workspace/proxy-test-server ./ 46 | COPY --from=builder /workspace/agent /workspace/manager ./ 47 | USER 65532:65532 48 | -------------------------------------------------------------------------------- /cmd/addon-agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "os" 10 | "sync/atomic" 11 | 12 | "k8s.io/client-go/kubernetes" 13 | "k8s.io/client-go/tools/clientcmd" 14 | "k8s.io/klog/v2" 15 | "k8s.io/klog/v2/textlogger" 16 | "open-cluster-management.io/addon-framework/pkg/lease" 17 | addonutils "open-cluster-management.io/addon-framework/pkg/utils" 18 | "open-cluster-management.io/cluster-proxy/pkg/common" 19 | "open-cluster-management.io/cluster-proxy/pkg/util" 20 | ctrl "sigs.k8s.io/controller-runtime" 21 | "sigs.k8s.io/controller-runtime/pkg/healthz" 22 | ) 23 | 24 | var ( 25 | hubKubeconfig string 26 | clusterName string 27 | proxyServerNamespace string 28 | enablePortForwardProxy bool 29 | ) 30 | 31 | // envKeyPodNamespace represents the environment variable key for the addon agent namespace. 32 | const envKeyPodNamespace = "POD_NAMESPACE" 33 | 34 | func main() { 35 | 36 | logger := textlogger.NewLogger(textlogger.NewConfig()) 37 | klog.SetOutput(os.Stdout) 38 | klog.InitFlags(flag.CommandLine) 39 | flag.StringVar(&hubKubeconfig, "hub-kubeconfig", "", 40 | "The kubeconfig to talk to hub cluster") 41 | flag.StringVar(&clusterName, "cluster-name", "", 42 | "The name of the managed cluster") 43 | flag.StringVar(&proxyServerNamespace, "proxy-server-namespace", "open-cluster-management-addon", 44 | "The namespace where proxy-server pod lives") 45 | flag.BoolVar(&enablePortForwardProxy, "enable-port-forward-proxy", false, 46 | "If true, running a local server forwarding tunnel shakes to proxy-server pods") 47 | flag.Parse() 48 | 49 | // pipe controller-runtime logs to klog 50 | ctrl.SetLogger(logger) 51 | 52 | cfg, err := clientcmd.BuildConfigFromFlags("", hubKubeconfig) 53 | if err != nil { 54 | panic(err) 55 | } 56 | cfg.UserAgent = "proxy-agent-addon-agent" 57 | 58 | spokeClient, err := kubernetes.NewForConfig(ctrl.GetConfigOrDie()) 59 | if err != nil { 60 | panic(fmt.Errorf("failed to create spoke client, err: %w", err)) 61 | } 62 | addonAgentNamespace := os.Getenv("POD_NAMESPACE") 63 | if len(addonAgentNamespace) == 0 { 64 | panic(fmt.Sprintf("Pod namespace is empty, please set the ENV for %s", envKeyPodNamespace)) 65 | } 66 | leaseUpdater := lease.NewLeaseUpdater(spokeClient, common.AddonName, addonAgentNamespace). 67 | WithHubLeaseConfig(cfg, clusterName) 68 | 69 | ctx := context.Background() 70 | 71 | readiness := &atomic.Value{} 72 | readiness.Store(true) 73 | if enablePortForwardProxy { 74 | readiness.Store(false) 75 | klog.Infof("Running local port-forward proxy") 76 | rr := util.NewRoundRobinLocalProxy( 77 | cfg, 78 | readiness, 79 | proxyServerNamespace, 80 | common.LabelKeyComponentName+"="+common.ComponentNameProxyServer, 81 | 8091, 82 | ) 83 | _, err := rr.Listen(ctx) 84 | if err != nil { 85 | panic(err) 86 | } 87 | } 88 | 89 | // If the certificates is changed, we need to restart the agent to load the new certificates. 90 | cc, err := addonutils.NewConfigChecker("certificates check", "/etc/tls/tls.crt", "/etc/tls/tls.key") 91 | if err != nil { 92 | klog.Fatalf("failed create certificates checker: %v", err) 93 | } 94 | cc.SetReload(true) 95 | 96 | go serveHealthProbes(ctx.Done(), ":8888", map[string]healthz.Checker{ 97 | "certificates": cc.Check, 98 | "port forward proxy readiness": func(_ *http.Request) error { 99 | if !readiness.Load().(bool) { 100 | return fmt.Errorf("not ready") 101 | } 102 | return nil 103 | }, 104 | }) 105 | 106 | klog.Infof("Starting lease updater") 107 | leaseUpdater.Start(ctx) 108 | <-ctx.Done() 109 | } 110 | 111 | // serveHealthProbes starts a server to check healthz and readyz probes 112 | func serveHealthProbes(stop <-chan struct{}, address string, healthCheckers map[string]healthz.Checker) { 113 | mux := http.NewServeMux() 114 | mux.Handle("/healthz", http.StripPrefix("/healthz", &healthz.Handler{Checks: healthCheckers})) 115 | 116 | server := http.Server{ 117 | Handler: mux, 118 | } 119 | 120 | ln, err := net.Listen("tcp", address) 121 | if err != nil { 122 | klog.Errorf("error listening on %s: %v", address, err) 123 | return 124 | } 125 | 126 | klog.Infof("heath probes server is running...") 127 | // Run server 128 | go func() { 129 | if err := server.Serve(ln); err != nil && err != http.ErrServerClosed { 130 | klog.Fatal(err) 131 | } 132 | }() 133 | 134 | // Shutdown the server when stop is closed 135 | <-stop 136 | if err := server.Shutdown(context.Background()); err != nil { 137 | klog.Fatal(err) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /examples/access-exported-services.md: -------------------------------------------------------------------------------- 1 | ```go 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | "time" 12 | 13 | "google.golang.org/grpc" 14 | grpccredentials "google.golang.org/grpc/credentials" 15 | "google.golang.org/grpc/keepalive" 16 | "k8s.io/client-go/tools/clientcmd" 17 | clusterproxyclient "open-cluster-management.io/cluster-proxy/client" 18 | 19 | konnectivityclient "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client" 20 | "sigs.k8s.io/apiserver-network-proxy/pkg/util" 21 | ) 22 | 23 | var kubeconfig string 24 | var managedcluster string 25 | var namespace string 26 | var serviceName string 27 | 28 | var proxyServerHost string 29 | var proxyServerPort string 30 | 31 | // Assumes that the cluster-proxy is installed in the multicluster-engine namespace. 32 | // `proxyCACert` could be found in Secret `proxy-server-ca` in the `multicluster-engine` namespace. 33 | var proxyCACertPath string 34 | 35 | // Assumes that the cluster-proxy is installed in the multicluster-engine namespace. 36 | // `proxyCert` and `proxyKey` could be found in Secret `proxy-client` in the `multicluster-engine` namespace. 37 | var proxyCertPath string 38 | var proxyKeyPath string 39 | 40 | // You can also run the following command to get credientials: 41 | /* 42 | k get secret -n multicluster-engine proxy-server-ca -o jsonpath='{.data.ca\.crt}' | base64 -D > ./temp/ca.crt && \ 43 | k get secret -n multicluster-engine proxy-client -o jsonpath='{.data.tls\.crt}' | base64 -D > ./temp/tls.crt && \ 44 | k get secret -n multicluster-engine proxy-client -o jsonpath='{.data.tls\.key}' | base64 -D > ./temp/tls.key 45 | */ 46 | 47 | var DefaultDialer = &net.Dialer{Timeout: 2 * time.Second, KeepAlive: 2 * time.Second} 48 | 49 | func main() { 50 | flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file") 51 | flag.StringVar(&managedcluster, "managed-cluster", "", "the name of the managed cluster") 52 | flag.StringVar(&namespace, "namespace", "", "the namespace of the target service") 53 | flag.StringVar(&serviceName, "service-name", "", "the name of the target service") 54 | 55 | flag.StringVar(&proxyServerHost, "host", "", "proxy server host") 56 | flag.StringVar(&proxyServerPort, "port", "", "proxy server port") 57 | flag.StringVar(&proxyCACertPath, "ca-cert", "", "the path to ca cert") 58 | flag.StringVar(&proxyCertPath, "cert", "", "the path to tls cert") 59 | flag.StringVar(&proxyKeyPath, "key", "", "the path to tls key") 60 | flag.Parse() 61 | 62 | // Step1: Get "proxy dialer" based on konnectivity client 63 | tlsCfg, err := util.GetClientTLSConfig(proxyCACertPath, proxyCertPath, proxyKeyPath, proxyServerHost, nil) 64 | if err != nil { 65 | panic(err) 66 | } 67 | proxyDialer, err := konnectivityclient.CreateSingleUseGrpcTunnel( 68 | context.TODO(), 69 | net.JoinHostPort(proxyServerHost, proxyServerPort), 70 | grpc.WithTransportCredentials(grpccredentials.NewTLS(tlsCfg)), 71 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 72 | Time: time.Second * 5, 73 | }), 74 | ) 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | // Step2: Get the "proxy Host" based on cluster-proxy client 80 | cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 81 | if err != nil { 82 | panic(err) 83 | } 84 | proxyHost, err := clusterproxyclient.GetProxyHost(context.Background(), cfg, managedcluster, namespace, serviceName) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | // Step3: Replace the default dialer with the proxy dialer 90 | tr := &http.Transport{ 91 | DialContext: proxyDialer.DialContext, 92 | TLSHandshakeTimeout: 2 * time.Second, 93 | } 94 | client := http.Client{Transport: tr} 95 | 96 | // Step4: Replace the host with the proxy host 97 | resp, err := client.Get("http://" + proxyHost + ":8000") 98 | if err != nil { 99 | panic(err) 100 | } 101 | defer resp.Body.Close() 102 | content, err := io.ReadAll(resp.Body) 103 | if err != nil { 104 | panic(err) 105 | } 106 | fmt.Print("response: ", string(content)) 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /examples/access-prometheus.md: -------------------------------------------------------------------------------- 1 | ```go 2 | package main 3 | 4 | import ( 5 | "context" 6 | "crypto/tls" 7 | "flag" 8 | "fmt" 9 | "net" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/pkg/errors" 14 | prometheusapi "github.com/prometheus/client_golang/api" 15 | prometheusv1 "github.com/prometheus/client_golang/api/prometheus/v1" 16 | "google.golang.org/grpc" 17 | grpccredentials "google.golang.org/grpc/credentials" 18 | "google.golang.org/grpc/keepalive" 19 | "k8s.io/client-go/tools/clientcmd" 20 | "k8s.io/client-go/transport" 21 | clusterproxyclient "open-cluster-management.io/cluster-proxy/client" 22 | konnectivityclient "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client" 23 | "sigs.k8s.io/apiserver-network-proxy/pkg/util" 24 | ) 25 | 26 | var kubeconfig string 27 | var managedcluster string 28 | var token string // The token is the token of `openshift-monitoring/prometheus-k8s` service account. 29 | 30 | var proxyServerHost string 31 | var proxyServerPort string 32 | 33 | // Assumes that the cluster-proxy is installed in the multicluster-engine namespace. 34 | // `proxyCACert` could be found in Secret `proxy-server-ca` in the `multicluster-engine` namespace. 35 | var proxyCACertPath string 36 | 37 | // Assumes that the cluster-proxy is installed in the multicluster-engine namespace. 38 | // `proxyCert` and `proxyKey` could be found in Secret `proxy-client` in the `multicluster-engine` namespace. 39 | var proxyCertPath string 40 | var proxyKeyPath string 41 | 42 | // You can also run the following command to get credientials: 43 | /* 44 | k get secret -n multicluster-engine proxy-server-ca -o jsonpath='{.data.ca\.crt}' | base64 -D > ./temp/ca.crt && \ 45 | k get secret -n multicluster-engine proxy-client -o jsonpath='{.data.tls\.crt}' | base64 -D > ./temp/tls.crt && \ 46 | k get secret -n multicluster-engine proxy-client -o jsonpath='{.data.tls\.key}' | base64 -D > ./temp/tls.key 47 | */ 48 | 49 | func main() { 50 | flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file") 51 | flag.StringVar(&managedcluster, "managed-cluster", "", "the name of the managed cluster") 52 | flag.StringVar(&token, "token", "", "the token of the managed cluster") 53 | flag.StringVar(&proxyServerHost, "host", "", "proxy server host") 54 | flag.StringVar(&proxyServerPort, "port", "", "proxy server port") 55 | flag.StringVar(&proxyCACertPath, "ca-cert", "", "the path to ca cert") 56 | flag.StringVar(&proxyCertPath, "cert", "", "the path to tls cert") 57 | flag.StringVar(&proxyKeyPath, "key", "", "the path to tls key") 58 | flag.Parse() 59 | 60 | // Step1: Get "proxy dialer" based on konnectivity client 61 | tlsCfg, err := util.GetClientTLSConfig(proxyCACertPath, proxyCertPath, proxyKeyPath, proxyServerHost, nil) 62 | if err != nil { 63 | panic(err) 64 | } 65 | proxyDialer, err := konnectivityclient.CreateSingleUseGrpcTunnel( 66 | context.TODO(), 67 | net.JoinHostPort(proxyServerHost, proxyServerPort), 68 | grpc.WithTransportCredentials(grpccredentials.NewTLS(tlsCfg)), 69 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 70 | Time: time.Second * 5, 71 | }), 72 | ) 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | // Step2: Get the "proxy Host" based on cluster-proxy client 78 | cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 79 | if err != nil { 80 | panic(err) 81 | } 82 | proxyHost, err := clusterproxyclient.GetProxyHost(context.Background(), cfg, managedcluster, "openshift-monitoring", "prometheus-k8s") 83 | if err != nil { 84 | panic(errors.Wrap(err, "failed to get proxy host")) 85 | } 86 | 87 | // Step3: Using the token as the bearer token to access the prometheus 88 | roundTripper, err := transport.NewBearerAuthWithRefreshRoundTripper("", token, &http.Transport{ 89 | DialContext: proxyDialer.DialContext, 90 | TLSHandshakeTimeout: 2 * time.Second, 91 | TLSClientConfig: &tls.Config{ 92 | InsecureSkipVerify: true, 93 | }, 94 | }) 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | // Step4: Replace the default dialer with the proxy dialer and replace the default host with the proxy host. 100 | pclient, err := prometheusapi.NewClient(prometheusapi.Config{ 101 | Address: "https://" + proxyHost + ":9091", 102 | RoundTripper: roundTripper, 103 | }) 104 | if err != nil { 105 | panic(err) 106 | } 107 | papiclient := prometheusv1.NewAPI(pclient) 108 | 109 | result, _, err := papiclient.Query(context.Background(), "machine_cpu_sockets", time.Now()) 110 | if err != nil { 111 | panic(errors.Wrap(err, "failed to query prometheus")) 112 | } 113 | fmt.Println(result) 114 | } 115 | ``` 116 | -------------------------------------------------------------------------------- /examples/test-client.md: -------------------------------------------------------------------------------- 1 | ```go 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "fmt" 8 | "net" 9 | "time" 10 | 11 | "google.golang.org/grpc" 12 | grpccredentials "google.golang.org/grpc/credentials" 13 | "google.golang.org/grpc/keepalive" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/tools/clientcmd" 17 | 18 | konnectivity "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client" 19 | "sigs.k8s.io/apiserver-network-proxy/pkg/util" 20 | ) 21 | 22 | var managedClusterKubeconfig string 23 | var managedClusterName string 24 | 25 | var proxyServerHost string 26 | var proxyServerPort string 27 | 28 | // Assumes that the cluster-proxy is installed in the open-cluster-management-addon namespace. 29 | // `proxyCACert` could be found in Secret `proxy-server-ca` in the `open-cluster-management-addon“ namespace. 30 | var proxyCACertPath string 31 | 32 | // Assumes that the cluster-proxy is installed in the open-cluster-management-addon namespace. 33 | // `proxyCert` and `proxyKey` could be found in Secret `proxy-client` in the `open-cluster-management-addon“ namespace. 34 | var proxyCertPath string 35 | var proxyKeyPath string 36 | 37 | func main() { 38 | flag.StringVar(&managedClusterKubeconfig, "kubeconfig", "", "the path to kubeconfig") 39 | flag.StringVar(&managedClusterName, "cluster", "", "the cluster name") 40 | flag.StringVar(&proxyServerHost, "host", "", "proxy server host") 41 | flag.StringVar(&proxyServerPort, "port", "", "proxy server port") 42 | flag.StringVar(&proxyCACertPath, "ca-cert", "", "the path to ca cert") 43 | flag.StringVar(&proxyCertPath, "cert", "", "the path to tls cert") 44 | flag.StringVar(&proxyKeyPath, "key", "", "the path to tls key") 45 | flag.Parse() 46 | 47 | cfg, err := clientcmd.BuildConfigFromFlags("", managedClusterKubeconfig) 48 | if err != nil { 49 | panic(err) 50 | } 51 | tlsCfg, err := util.GetClientTLSConfig(proxyCACertPath, proxyCertPath, proxyKeyPath, proxyServerHost, nil) 52 | if err != nil { 53 | panic(err) 54 | } 55 | dialerTunnel, err := konnectivity.CreateSingleUseGrpcTunnel( 56 | context.TODO(), 57 | net.JoinHostPort(proxyServerHost, proxyServerPort), 58 | grpc.WithTransportCredentials(grpccredentials.NewTLS(tlsCfg)), 59 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 60 | Time: time.Second * 5, 61 | }), 62 | ) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | cfg.Host = managedClusterName 68 | // TODO: flexible client-side tls server name validation 69 | cfg.TLSClientConfig.Insecure = true 70 | cfg.TLSClientConfig.CAData = nil 71 | cfg.TLSClientConfig.CAFile = "" 72 | cfg.Dial = dialerTunnel.DialContext 73 | client := kubernetes.NewForConfigOrDie(cfg) 74 | 75 | ns, err := client.CoreV1(). 76 | Namespaces(). 77 | Get(context.TODO(), "default", metav1.GetOptions{}) 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | fmt.Printf("%v\n", ns) 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module open-cluster-management.io/cluster-proxy 2 | 3 | go 1.23.6 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.17.1 7 | github.com/onsi/gomega v1.32.0 8 | github.com/openshift/library-go v0.0.0-20240621150525-4bb4238aef81 9 | github.com/pkg/errors v0.9.1 10 | github.com/stretchr/testify v1.9.0 11 | google.golang.org/grpc v1.62.1 12 | k8s.io/api v0.30.2 13 | k8s.io/apimachinery v0.30.2 14 | k8s.io/client-go v0.30.2 15 | k8s.io/klog/v2 v2.120.1 16 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57 17 | open-cluster-management.io/addon-framework v0.12.0 18 | open-cluster-management.io/api v0.16.1 19 | open-cluster-management.io/sdk-go v0.16.0 20 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.2 21 | sigs.k8s.io/controller-runtime v0.18.4 22 | ) 23 | 24 | require ( 25 | github.com/BurntSushi/toml v1.3.2 // indirect 26 | github.com/Masterminds/goutils v1.1.1 // indirect 27 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 28 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 29 | github.com/beorn7/perks v1.0.1 // indirect 30 | github.com/blang/semver/v4 v4.0.0 // indirect 31 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 32 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 35 | github.com/evanphx/json-patch v5.7.0+incompatible // indirect 36 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 37 | github.com/fatih/structs v1.1.0 // indirect 38 | github.com/fsnotify/fsnotify v1.7.0 // indirect 39 | github.com/go-logr/logr v1.4.1 // indirect 40 | github.com/go-logr/zapr v1.3.0 // indirect 41 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 42 | github.com/go-openapi/jsonreference v0.20.2 // indirect 43 | github.com/go-openapi/swag v0.22.3 // indirect 44 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 45 | github.com/gobwas/glob v0.2.3 // indirect 46 | github.com/gogo/protobuf v1.3.2 // indirect 47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 48 | github.com/golang/protobuf v1.5.4 // indirect 49 | github.com/google/gnostic-models v0.6.8 // indirect 50 | github.com/google/go-cmp v0.6.0 // indirect 51 | github.com/google/gofuzz v1.2.0 // indirect 52 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 53 | github.com/google/uuid v1.6.0 // indirect 54 | github.com/huandu/xstrings v1.4.0 // indirect 55 | github.com/imdario/mergo v0.3.16 // indirect 56 | github.com/josharian/intern v1.0.0 // indirect 57 | github.com/json-iterator/go v1.1.12 // indirect 58 | github.com/mailru/easyjson v0.7.7 // indirect 59 | github.com/mitchellh/copystructure v1.2.0 // indirect 60 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 61 | github.com/moby/spdystream v0.2.0 // indirect 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 63 | github.com/modern-go/reflect2 v1.0.2 // indirect 64 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 65 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 66 | github.com/pmezard/go-difflib v1.0.0 // indirect 67 | github.com/prometheus/client_golang v1.19.0 // indirect 68 | github.com/prometheus/client_model v0.5.0 // indirect 69 | github.com/prometheus/common v0.48.0 // indirect 70 | github.com/prometheus/procfs v0.12.0 // indirect 71 | github.com/shopspring/decimal v1.3.1 // indirect 72 | github.com/spf13/cast v1.5.0 // indirect 73 | github.com/spf13/pflag v1.0.5 // indirect 74 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 75 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 76 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 77 | go.uber.org/multierr v1.11.0 // indirect 78 | go.uber.org/zap v1.27.0 // indirect 79 | golang.org/x/crypto v0.36.0 // indirect 80 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect 81 | golang.org/x/net v0.34.0 // indirect 82 | golang.org/x/oauth2 v0.28.0 // indirect 83 | golang.org/x/sys v0.31.0 // indirect 84 | golang.org/x/term v0.30.0 // indirect 85 | golang.org/x/text v0.23.0 // indirect 86 | golang.org/x/time v0.5.0 // indirect 87 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 88 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 89 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect 90 | google.golang.org/protobuf v1.33.0 // indirect 91 | gopkg.in/inf.v0 v0.9.1 // indirect 92 | gopkg.in/yaml.v2 v2.4.0 // indirect 93 | gopkg.in/yaml.v3 v3.0.1 // indirect 94 | helm.sh/helm/v3 v3.14.2 // indirect 95 | k8s.io/apiextensions-apiserver v0.30.2 // indirect 96 | k8s.io/apiserver v0.30.2 // indirect 97 | k8s.io/component-base v0.30.2 // indirect 98 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 99 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 100 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 101 | sigs.k8s.io/yaml v1.4.0 // indirect 102 | ) 103 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-cluster-management-io/cluster-proxy/5e977b5eadd2330ac0896b742c1e22ea0bab0798/hack/boilerplate.go.txt -------------------------------------------------------------------------------- /hack/crd/addon/clustermanagementaddon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: clustermanagementaddons.addon.open-cluster-management.io 5 | spec: 6 | group: addon.open-cluster-management.io 7 | names: 8 | kind: ClusterManagementAddOn 9 | listKind: ClusterManagementAddOnList 10 | plural: clustermanagementaddons 11 | singular: clustermanagementaddon 12 | scope: Cluster 13 | preserveUnknownFields: false 14 | versions: 15 | - additionalPrinterColumns: 16 | - jsonPath: .spec.addOnMeta.displayName 17 | name: DISPLAY NAME 18 | type: string 19 | - jsonPath: .spec.addOnConfiguration.crdName 20 | name: CRD NAME 21 | type: string 22 | name: v1alpha1 23 | schema: 24 | openAPIV3Schema: 25 | description: ClusterManagementAddOn represents the registration of an add-on to the cluster manager. This resource allows the user to discover which add-on is available for the cluster manager and also provides metadata information about the add-on. This resource also provides a linkage to ManagedClusterAddOn, the name of the ClusterManagementAddOn resource will be used for the namespace-scoped ManagedClusterAddOn resource. ClusterManagementAddOn is a cluster-scoped resource. 26 | type: object 27 | properties: 28 | apiVersion: 29 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 30 | type: string 31 | kind: 32 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: spec represents a desired configuration for the agent on the cluster management add-on. 38 | type: object 39 | properties: 40 | addOnConfiguration: 41 | description: 'Deprecated: Use supportedConfigs filed instead addOnConfiguration is a reference to configuration information for the add-on. In scenario where a multiple add-ons share the same add-on CRD, multiple ClusterManagementAddOn resources need to be created and reference the same AddOnConfiguration.' 42 | type: object 43 | properties: 44 | crName: 45 | description: crName is the name of the CR used to configure instances of the managed add-on. This field should be configured if add-on CR have a consistent name across the all of the ManagedCluster instaces. 46 | type: string 47 | crdName: 48 | description: crdName is the name of the CRD used to configure instances of the managed add-on. This field should be configured if the add-on have a CRD that controls the configuration of the add-on. 49 | type: string 50 | lastObservedGeneration: 51 | description: lastObservedGeneration is the observed generation of the custom resource for the configuration of the addon. 52 | type: integer 53 | format: int64 54 | addOnMeta: 55 | description: addOnMeta is a reference to the metadata information for the add-on. 56 | type: object 57 | properties: 58 | description: 59 | description: description represents the detailed description of the add-on. 60 | type: string 61 | displayName: 62 | description: displayName represents the name of add-on that will be displayed. 63 | type: string 64 | supportedConfigs: 65 | description: supportedConfigs is a list of configuration types supported by add-on. An empty list means the add-on does not require configurations. The default is an empty list 66 | type: array 67 | items: 68 | description: ConfigMeta represents a collection of metadata information for add-on configuration. 69 | type: object 70 | required: 71 | - resource 72 | properties: 73 | defaultConfig: 74 | description: defaultConfig represents the namespace and name of the default add-on configuration. In scenario where all add-ons have a same configuration. 75 | type: object 76 | required: 77 | - name 78 | properties: 79 | name: 80 | description: name of the add-on configuration. 81 | type: string 82 | minLength: 1 83 | namespace: 84 | description: namespace of the add-on configuration. If this field is not set, the configuration is in the cluster scope. 85 | type: string 86 | group: 87 | description: group of the add-on configuration. 88 | type: string 89 | resource: 90 | description: resource of the add-on configuration. 91 | type: string 92 | minLength: 1 93 | status: 94 | description: status represents the current status of cluster management add-on. 95 | type: object 96 | served: true 97 | storage: true 98 | subresources: 99 | status: {} 100 | status: 101 | acceptedNames: 102 | kind: "" 103 | plural: "" 104 | conditions: [] 105 | storedVersions: [] 106 | -------------------------------------------------------------------------------- /hack/picture/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-cluster-management-io/cluster-proxy/5e977b5eadd2330ac0896b742c1e22ea0bab0798/hack/picture/arch.png -------------------------------------------------------------------------------- /hack/samples/clustermanagementaddon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: addon.open-cluster-management.io/v1alpha1 2 | kind: ClusterManagementAddOn 3 | metadata: 4 | name: cluster-proxy 5 | spec: 6 | addOnMeta: 7 | displayName: cluster-proxy 8 | description: cluster-proxy 9 | addOnConfiguration: 10 | crdName: managedproxyconfigurations.proxy.open-cluster-management.io 11 | crName: cluster-proxy 12 | -------------------------------------------------------------------------------- /hack/samples/managedcluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.open-cluster-management.io/v1 2 | kind: ManagedCluster 3 | metadata: 4 | name: local 5 | spec: 6 | hubAcceptsClient: false 7 | -------------------------------------------------------------------------------- /hack/samples/managedclusteraddon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: addon.open-cluster-management.io/v1alpha1 2 | kind: ManagedClusterAddOn 3 | metadata: 4 | name: cluster-proxy 5 | namespace: cluster-test2 6 | spec: 7 | installNamespace: "open-cluster-management-cluster-proxy" 8 | -------------------------------------------------------------------------------- /hack/samples/managedproxyconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: proxy.open-cluster-management.io/v1alpha1 2 | kind: ManagedProxyConfiguration 3 | metadata: 4 | name: cluster-proxy 5 | spec: 6 | authentication: 7 | certificateMounting: 8 | secrets: {} 9 | certificateSigning: 10 | type: SelfSigned 11 | proxyServer: 12 | image: "yue9944882/proxy-server-amd64:v0.0.22" 13 | entrypoint: 14 | type: LoadBalancerService 15 | loadBalancerService: {} 16 | proxyAgent: 17 | image: "yue9944882/proxy-agent-amd64:v0.0.22" 18 | -------------------------------------------------------------------------------- /pkg/apis/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | //go:generate apiregister-gen -g client-gen -h ../../boilerplate.go.txt 16 | 17 | // 18 | // +domain=open-cluster-management.io 19 | 20 | package apis 21 | -------------------------------------------------------------------------------- /pkg/apis/proxy/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The KubeVela Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | // +k8s:deepcopy-gen=package,register 16 | // +groupName=proxy.open-cluster-management.io 17 | 18 | // Package api is the internal version of the API. 19 | package cluster 20 | -------------------------------------------------------------------------------- /pkg/apis/proxy/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | // Api versions allow the api contract for a resource to be changed while keeping 16 | // backward compatibility by support multiple concurrent versions 17 | // of the same resource 18 | 19 | // +k8s:openapi-gen=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +k8s:defaulter-gen=TypeMeta 22 | // +groupName=proxy.open-cluster-management.io 23 | package v1alpha1 24 | -------------------------------------------------------------------------------- /pkg/apis/proxy/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the proxy v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=proxy.open-cluster-management.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "proxy.open-cluster-management.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | 37 | SchemeGroupVersion = GroupVersion 38 | ) 39 | 40 | func Resource(resource string) schema.GroupResource { 41 | return schema.GroupResource{ 42 | Group: "proxy.open-cluster-management.io", 43 | Resource: resource, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/apis/proxy/v1alpha1/managedproxyserviceresolver_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | func init() { 24 | SchemeBuilder.Register(&ManagedProxyServiceResolver{}, &ManagedProxyServiceResolverList{}) 25 | } 26 | 27 | //+kubebuilder:object:root=true 28 | //+kubebuilder:subresource:status 29 | //+kubebuilder:resource:scope=Cluster 30 | 31 | // +genclient 32 | // +genclient:nonNamespaced 33 | // ManagedProxyServiceResolver defines a target service that need to expose from a set of managed clusters to the hub. 34 | // To access a target service on a managed cluster from hub. First, users need to apply a proper ManagedProxyServiceResolver. 35 | // The managed cluster should match the ManagedClusterSet in the ManagedProxyServiceResolver.Spec. The serviceNamespace and serviceName should also match the target service. 36 | // A usage example: /examples/access-other-services/main.go 37 | type ManagedProxyServiceResolver struct { 38 | metav1.TypeMeta `json:",inline"` 39 | metav1.ObjectMeta `json:"metadata,omitempty"` 40 | 41 | Spec ManagedProxyServiceResolverSpec `json:"spec,omitempty"` 42 | Status ManagedProxyServiceResolverStatus `json:"status,omitempty"` 43 | } 44 | 45 | //+kubebuilder:object:root=true 46 | 47 | // ManagedProxyServiceResolverList contains a list of ManagedProxyServiceResolver 48 | type ManagedProxyServiceResolverList struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ListMeta `json:"metadata,omitempty"` 51 | Items []ManagedProxyServiceResolver `json:"items"` 52 | } 53 | 54 | // ManagedProxyServiceResolverSpec defines the desired state of ManagedProxyServiceResolver. 55 | type ManagedProxyServiceResolverSpec struct { 56 | // ManagedClusterSelector selects a set of managed clusters. 57 | // +required 58 | ManagedClusterSelector ManagedClusterSelector `json:"managedClusterSelector"` 59 | 60 | // ServiceSelector selects a service. 61 | // +required 62 | ServiceSelector ServiceSelector `json:"serviceSelector"` 63 | } 64 | 65 | // ManagedClusterSelectorType is the type of ManagedClusterSelector. 66 | // +kubebuilder:validation:Enum=ManagedClusterSet 67 | type ManagedClusterSelectorType string 68 | 69 | var ( 70 | // ManagedClusterSetSelectorType indicates the selector is a ManagedClusterSet. 71 | // In this type, the manageclusterset field of the selector is required. 72 | ManagedClusterSelectorTypeClusterSet ManagedClusterSelectorType = "ManagedClusterSet" 73 | ) 74 | 75 | type ManagedClusterSelector struct { 76 | // Type represents the type of the selector. Now only ManagedClusterSet is supported. 77 | // +optional 78 | // +kubebuilder:default=ManagedClusterSet 79 | Type ManagedClusterSelectorType `json:"type,omitempty"` 80 | 81 | // ManagedClusterSet defines a set of managed clusters that need to expose the service. 82 | // +optional 83 | ManagedClusterSet *ManagedClusterSet `json:"managedClusterSet,omitempty"` 84 | } 85 | 86 | // ManagedClusterSet defines the name of a managed cluster set. 87 | type ManagedClusterSet struct { 88 | // Name is the name of the managed cluster set. 89 | // +required 90 | Name string `json:"name"` 91 | } 92 | 93 | // ServiceSelectorType is the type of ServiceSelector. 94 | // +kubebuilder:validation:Enum=ServiceRef 95 | type ServiceSelectorType string 96 | 97 | var ( 98 | // ServiceSelectorTypeServiceRef indicates the selector requires serviceNamespace and serviceName fields. 99 | ServiceSelectorTypeServiceRef ServiceSelectorType = "ServiceRef" 100 | ) 101 | 102 | type ServiceSelector struct { 103 | // Type represents the type of the selector. Now only ServiceRef type is supported. 104 | // +optional 105 | // +kubebuilder:default=ServiceRef 106 | Type ServiceSelectorType `json:"type,omitempty"` 107 | 108 | // ServiceRef defines a service in a namespace. 109 | // +optional 110 | ServiceRef *ServiceRef `json:"serviceRef,omitempty"` 111 | } 112 | 113 | // ServiceRef represents a service in a namespace. 114 | type ServiceRef struct { 115 | // Namespace represents the namespace of the service. 116 | // +required 117 | Namespace string `json:"namespace"` 118 | 119 | // Name represents the name of the service. 120 | // +required 121 | Name string `json:"name"` 122 | } 123 | 124 | // ManagedProxyServiceResolverStatus defines the observed state of ManagedProxyServiceResolver. 125 | type ManagedProxyServiceResolverStatus struct { 126 | // Conditions contains the different condition statuses for this ManagedProxyServiceResolver. 127 | Conditions []metav1.Condition `json:"conditions"` 128 | } 129 | 130 | const ( 131 | ConditionTypeServiceResolverAvaliable = "ServiceResolverAvaliable" 132 | ) 133 | -------------------------------------------------------------------------------- /pkg/common/constants.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "k8s.io/apimachinery/pkg/util/sets" 4 | 5 | const ( 6 | AddonName = "cluster-proxy" 7 | AddonFullName = "open-cluster-management:cluster-proxy" 8 | 9 | ComponentNameProxyAgentServer = "proxy-agent-server" 10 | ComponentNameProxyServer = "proxy-server" 11 | ComponentNameProxyAgent = "proxy-agent" 12 | ComponentNameProxyClient = "proxy-client" 13 | ) 14 | 15 | var ( 16 | AllComponentNames = sets.NewString( 17 | ComponentNameProxyAgentServer, 18 | ComponentNameProxyServer, 19 | ComponentNameProxyClient, 20 | ) 21 | ) 22 | 23 | const ( 24 | SubjectGroupClusterProxy = "open-cluster-management:cluster-proxy" 25 | SubjectUserClusterProxyAgent = "open-cluster-management:cluster-proxy:proxy-agent" 26 | SubjectUserClusterProxyServer = "open-cluster-management:cluster-proxy:proxy-server" 27 | SubjectUserClusterAgentServer = "open-cluster-management:cluster-proxy:agent-server" 28 | SubjectUserClusterAddonAgent = "open-cluster-management:cluster-proxy:addon-agent" 29 | ) 30 | 31 | const ( 32 | LabelKeyComponentName = "proxy.open-cluster-management.io/component-name" 33 | AnnotationKeyConfigurationGeneration = "proxy.open-cluster-management.io/configuration-generation" 34 | ) 35 | 36 | const ( 37 | AgentClientSecretName = "agent-client" 38 | ) 39 | -------------------------------------------------------------------------------- /pkg/common/help.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "reflect" 8 | 9 | certutil "k8s.io/client-go/util/cert" 10 | ) 11 | 12 | func MergeCertificateData(caBundles ...[]byte) ([]byte, error) { 13 | var all []*x509.Certificate 14 | for _, caBundle := range caBundles { 15 | if len(caBundle) == 0 { 16 | continue 17 | } 18 | 19 | certs, err := certutil.ParseCertsPEM(caBundle) 20 | if err != nil { 21 | return []byte{}, err 22 | } 23 | all = append(all, certs...) 24 | } 25 | 26 | // remove duplicated cert 27 | var merged []*x509.Certificate 28 | for i := range all { 29 | found := false 30 | for j := range merged { 31 | if reflect.DeepEqual(all[i].Raw, merged[j].Raw) { 32 | found = true 33 | break 34 | } 35 | } 36 | if !found { 37 | merged = append(merged, all[i]) 38 | } 39 | } 40 | 41 | // encode the merged certificates 42 | b := bytes.Buffer{} 43 | for _, cert := range merged { 44 | if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { 45 | return []byte{}, err 46 | } 47 | } 48 | return b.Bytes(), nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/config/agent.go: -------------------------------------------------------------------------------- 1 | // TODO (skeeey) move this to the util package 2 | package config 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | 8 | addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" 9 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 10 | 11 | "k8s.io/klog/v2" 12 | ) 13 | 14 | // AgentImageName is the image of the spoke addon agent. 15 | // Can be override via "--agent-image-name" on the hub addon manager. 16 | var AgentImageName string 17 | 18 | var DefaultAddonInstallNamespace = "open-cluster-management-cluster-proxy" 19 | 20 | func GetParsedAgentImage(defaultAgentImageName string) (string, string, string, error) { 21 | if len(AgentImageName) == 0 { 22 | klog.InfoS("AgentImageName is not set, use default value", "defaultAgentImageName", defaultAgentImageName) 23 | AgentImageName = defaultAgentImageName 24 | } 25 | imgParts := strings.Split(AgentImageName, "/") 26 | if len(imgParts) != 2 && len(imgParts) != 3 { 27 | // image name without registry is also legal. 28 | return "", "", "", fmt.Errorf("invalid agent image name: %s", AgentImageName) 29 | } 30 | 31 | registry := strings.Join(imgParts[0:len(imgParts)-1], "/") 32 | 33 | parts := strings.Split(imgParts[len(imgParts)-1], ":") 34 | image := parts[0] 35 | 36 | tag := "latest" 37 | if len(parts) >= 2 { 38 | tag = parts[len(parts)-1] 39 | } 40 | 41 | return registry, image, tag, nil 42 | } 43 | 44 | func IsManagedProxyConfiguration(gr addonv1alpha1.ConfigGroupResource) bool { 45 | if gr.Group != proxyv1alpha1.GroupVersion.Group { 46 | return false 47 | } 48 | 49 | if gr.Resource != "managedproxyconfigurations" { 50 | return false 51 | } 52 | 53 | return true 54 | } 55 | -------------------------------------------------------------------------------- /pkg/config/agent_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetParsedAgentImage(t *testing.T) { 8 | testcases := []struct { 9 | agentImageName string 10 | expectErr bool 11 | registry string 12 | image string 13 | tag string 14 | }{ 15 | { 16 | // no registry 17 | // no tag 18 | "open-cluster-management.io/cluster-proxy-agent", 19 | false, 20 | "open-cluster-management.io", 21 | "cluster-proxy-agent", 22 | "latest", 23 | }, 24 | { 25 | // no tag 26 | "quay.io/open-cluster-management.io/cluster-proxy-agent", 27 | false, 28 | "quay.io/open-cluster-management.io", 29 | "cluster-proxy-agent", 30 | "latest", 31 | }, 32 | { 33 | "quay.io/open-cluster-management.io/cluster-proxy-agent:v0.1.0", 34 | false, 35 | "quay.io/open-cluster-management.io", 36 | "cluster-proxy-agent", 37 | "v0.1.0", 38 | }, 39 | { 40 | // registry with port 41 | "quay.io:443/open-cluster-management.io/cluster-proxy-agent:v0.1.0", 42 | false, 43 | "quay.io:443/open-cluster-management.io", 44 | "cluster-proxy-agent", 45 | "v0.1.0", 46 | }, 47 | { 48 | // registry with port 49 | // no tag 50 | "quay.io:443/open-cluster-management.io/cluster-proxy-agent", 51 | false, 52 | "quay.io:443/open-cluster-management.io", 53 | "cluster-proxy-agent", 54 | "latest", 55 | }, 56 | { 57 | // empty image name 58 | "", 59 | false, 60 | "quay.io/open-cluster-management.io", 61 | "cluster-proxy-agent", 62 | "latest", 63 | }, 64 | { 65 | // wrong image name 66 | "foo", 67 | true, 68 | "", 69 | "", 70 | "", 71 | }, { 72 | // wrong image name 73 | "foo/foo/foo/foo", 74 | true, 75 | "", 76 | "", 77 | "", 78 | }, 79 | } 80 | 81 | for _, c := range testcases { 82 | AgentImageName = c.agentImageName 83 | r, i, tag, err := GetParsedAgentImage("quay.io/open-cluster-management.io/cluster-proxy-agent") 84 | if err != nil { 85 | if c.expectErr { 86 | continue 87 | } 88 | t.Errorf("GetParsedAgentImage() error: %v", err) 89 | } 90 | 91 | if r != c.registry || i != c.image || tag != c.tag { 92 | t.Errorf("expect %s, %s, %s, but get %s, %s, %s", c.registry, c.image, c.tag, r, i, tag) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package versioned 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | 9 | discovery "k8s.io/client-go/discovery" 10 | rest "k8s.io/client-go/rest" 11 | flowcontrol "k8s.io/client-go/util/flowcontrol" 12 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned/typed/proxy/v1alpha1" 13 | ) 14 | 15 | type Interface interface { 16 | Discovery() discovery.DiscoveryInterface 17 | ProxyV1alpha1() proxyv1alpha1.ProxyV1alpha1Interface 18 | } 19 | 20 | // Clientset contains the clients for groups. Each group has exactly one 21 | // version included in a Clientset. 22 | type Clientset struct { 23 | *discovery.DiscoveryClient 24 | proxyV1alpha1 *proxyv1alpha1.ProxyV1alpha1Client 25 | } 26 | 27 | // ProxyV1alpha1 retrieves the ProxyV1alpha1Client 28 | func (c *Clientset) ProxyV1alpha1() proxyv1alpha1.ProxyV1alpha1Interface { 29 | return c.proxyV1alpha1 30 | } 31 | 32 | // Discovery retrieves the DiscoveryClient 33 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 34 | if c == nil { 35 | return nil 36 | } 37 | return c.DiscoveryClient 38 | } 39 | 40 | // NewForConfig creates a new Clientset for the given config. 41 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 42 | // NewForConfig will generate a rate-limiter in configShallowCopy. 43 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 44 | // where httpClient was generated with rest.HTTPClientFor(c). 45 | func NewForConfig(c *rest.Config) (*Clientset, error) { 46 | configShallowCopy := *c 47 | 48 | // share the transport between all clients 49 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return NewForConfigAndClient(&configShallowCopy, httpClient) 55 | } 56 | 57 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 58 | // Note the http client provided takes precedence over the configured transport values. 59 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 60 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 61 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 62 | configShallowCopy := *c 63 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 64 | if configShallowCopy.Burst <= 0 { 65 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 66 | } 67 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 68 | } 69 | 70 | var cs Clientset 71 | var err error 72 | cs.proxyV1alpha1, err = proxyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return &cs, nil 82 | } 83 | 84 | // NewForConfigOrDie creates a new Clientset for the given config and 85 | // panics if there is an error in the config. 86 | func NewForConfigOrDie(c *rest.Config) *Clientset { 87 | cs, err := NewForConfig(c) 88 | if err != nil { 89 | panic(err) 90 | } 91 | return cs 92 | } 93 | 94 | // New creates a new Clientset for the given RESTClient. 95 | func New(c rest.Interface) *Clientset { 96 | var cs Clientset 97 | cs.proxyV1alpha1 = proxyv1alpha1.New(c) 98 | 99 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 100 | return &cs 101 | } 102 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated clientset. 4 | package versioned 5 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | "k8s.io/apimachinery/pkg/runtime" 7 | "k8s.io/apimachinery/pkg/watch" 8 | "k8s.io/client-go/discovery" 9 | fakediscovery "k8s.io/client-go/discovery/fake" 10 | "k8s.io/client-go/testing" 11 | clientset "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 12 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned/typed/proxy/v1alpha1" 13 | fakeproxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned/typed/proxy/v1alpha1/fake" 14 | ) 15 | 16 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 17 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 18 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 19 | // for a real clientset and is mostly useful in simple unit tests. 20 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 21 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 22 | for _, obj := range objects { 23 | if err := o.Add(obj); err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | cs := &Clientset{tracker: o} 29 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 30 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 31 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 32 | gvr := action.GetResource() 33 | ns := action.GetNamespace() 34 | watch, err := o.Watch(gvr, ns) 35 | if err != nil { 36 | return false, nil, err 37 | } 38 | return true, watch, nil 39 | }) 40 | 41 | return cs 42 | } 43 | 44 | // Clientset implements clientset.Interface. Meant to be embedded into a 45 | // struct to get a default implementation. This makes faking out just the method 46 | // you want to test easier. 47 | type Clientset struct { 48 | testing.Fake 49 | discovery *fakediscovery.FakeDiscovery 50 | tracker testing.ObjectTracker 51 | } 52 | 53 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 54 | return c.discovery 55 | } 56 | 57 | func (c *Clientset) Tracker() testing.ObjectTracker { 58 | return c.tracker 59 | } 60 | 61 | var ( 62 | _ clientset.Interface = &Clientset{} 63 | _ testing.FakeClient = &Clientset{} 64 | ) 65 | 66 | // ProxyV1alpha1 retrieves the ProxyV1alpha1Client 67 | func (c *Clientset) ProxyV1alpha1() proxyv1alpha1.ProxyV1alpha1Interface { 68 | return &fakeproxyv1alpha1.FakeProxyV1alpha1{Fake: &c.Fake} 69 | } 70 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated fake clientset. 4 | package fake 5 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | runtime "k8s.io/apimachinery/pkg/runtime" 8 | schema "k8s.io/apimachinery/pkg/runtime/schema" 9 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 10 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 11 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 12 | ) 13 | 14 | var scheme = runtime.NewScheme() 15 | var codecs = serializer.NewCodecFactory(scheme) 16 | 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | proxyv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package contains the scheme of the automatically generated clientset. 4 | package scheme 5 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package scheme 4 | 5 | import ( 6 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | runtime "k8s.io/apimachinery/pkg/runtime" 8 | schema "k8s.io/apimachinery/pkg/runtime/schema" 9 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 10 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 11 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 12 | ) 13 | 14 | var Scheme = runtime.NewScheme() 15 | var Codecs = serializer.NewCodecFactory(Scheme) 16 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | proxyv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(Scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated typed clients. 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // Package fake has the automatically generated clients. 4 | package fake 5 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/fake/fake_managedproxyconfiguration.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | "context" 7 | 8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | labels "k8s.io/apimachinery/pkg/labels" 10 | schema "k8s.io/apimachinery/pkg/runtime/schema" 11 | types "k8s.io/apimachinery/pkg/types" 12 | watch "k8s.io/apimachinery/pkg/watch" 13 | testing "k8s.io/client-go/testing" 14 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 15 | ) 16 | 17 | // FakeManagedProxyConfigurations implements ManagedProxyConfigurationInterface 18 | type FakeManagedProxyConfigurations struct { 19 | Fake *FakeProxyV1alpha1 20 | } 21 | 22 | var managedproxyconfigurationsResource = schema.GroupVersionResource{Group: "proxy.open-cluster-management.io", Version: "v1alpha1", Resource: "managedproxyconfigurations"} 23 | 24 | var managedproxyconfigurationsKind = schema.GroupVersionKind{Group: "proxy.open-cluster-management.io", Version: "v1alpha1", Kind: "ManagedProxyConfiguration"} 25 | 26 | // Get takes name of the managedProxyConfiguration, and returns the corresponding managedProxyConfiguration object, and an error if there is any. 27 | func (c *FakeManagedProxyConfigurations) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ManagedProxyConfiguration, err error) { 28 | obj, err := c.Fake. 29 | Invokes(testing.NewRootGetAction(managedproxyconfigurationsResource, name), &v1alpha1.ManagedProxyConfiguration{}) 30 | if obj == nil { 31 | return nil, err 32 | } 33 | return obj.(*v1alpha1.ManagedProxyConfiguration), err 34 | } 35 | 36 | // List takes label and field selectors, and returns the list of ManagedProxyConfigurations that match those selectors. 37 | func (c *FakeManagedProxyConfigurations) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ManagedProxyConfigurationList, err error) { 38 | obj, err := c.Fake. 39 | Invokes(testing.NewRootListAction(managedproxyconfigurationsResource, managedproxyconfigurationsKind, opts), &v1alpha1.ManagedProxyConfigurationList{}) 40 | if obj == nil { 41 | return nil, err 42 | } 43 | 44 | label, _, _ := testing.ExtractFromListOptions(opts) 45 | if label == nil { 46 | label = labels.Everything() 47 | } 48 | list := &v1alpha1.ManagedProxyConfigurationList{ListMeta: obj.(*v1alpha1.ManagedProxyConfigurationList).ListMeta} 49 | for _, item := range obj.(*v1alpha1.ManagedProxyConfigurationList).Items { 50 | if label.Matches(labels.Set(item.Labels)) { 51 | list.Items = append(list.Items, item) 52 | } 53 | } 54 | return list, err 55 | } 56 | 57 | // Watch returns a watch.Interface that watches the requested managedProxyConfigurations. 58 | func (c *FakeManagedProxyConfigurations) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 59 | return c.Fake. 60 | InvokesWatch(testing.NewRootWatchAction(managedproxyconfigurationsResource, opts)) 61 | } 62 | 63 | // Create takes the representation of a managedProxyConfiguration and creates it. Returns the server's representation of the managedProxyConfiguration, and an error, if there is any. 64 | func (c *FakeManagedProxyConfigurations) Create(ctx context.Context, managedProxyConfiguration *v1alpha1.ManagedProxyConfiguration, opts v1.CreateOptions) (result *v1alpha1.ManagedProxyConfiguration, err error) { 65 | obj, err := c.Fake. 66 | Invokes(testing.NewRootCreateAction(managedproxyconfigurationsResource, managedProxyConfiguration), &v1alpha1.ManagedProxyConfiguration{}) 67 | if obj == nil { 68 | return nil, err 69 | } 70 | return obj.(*v1alpha1.ManagedProxyConfiguration), err 71 | } 72 | 73 | // Update takes the representation of a managedProxyConfiguration and updates it. Returns the server's representation of the managedProxyConfiguration, and an error, if there is any. 74 | func (c *FakeManagedProxyConfigurations) Update(ctx context.Context, managedProxyConfiguration *v1alpha1.ManagedProxyConfiguration, opts v1.UpdateOptions) (result *v1alpha1.ManagedProxyConfiguration, err error) { 75 | obj, err := c.Fake. 76 | Invokes(testing.NewRootUpdateAction(managedproxyconfigurationsResource, managedProxyConfiguration), &v1alpha1.ManagedProxyConfiguration{}) 77 | if obj == nil { 78 | return nil, err 79 | } 80 | return obj.(*v1alpha1.ManagedProxyConfiguration), err 81 | } 82 | 83 | // UpdateStatus was generated because the type contains a Status member. 84 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 85 | func (c *FakeManagedProxyConfigurations) UpdateStatus(ctx context.Context, managedProxyConfiguration *v1alpha1.ManagedProxyConfiguration, opts v1.UpdateOptions) (*v1alpha1.ManagedProxyConfiguration, error) { 86 | obj, err := c.Fake. 87 | Invokes(testing.NewRootUpdateSubresourceAction(managedproxyconfigurationsResource, "status", managedProxyConfiguration), &v1alpha1.ManagedProxyConfiguration{}) 88 | if obj == nil { 89 | return nil, err 90 | } 91 | return obj.(*v1alpha1.ManagedProxyConfiguration), err 92 | } 93 | 94 | // Delete takes name of the managedProxyConfiguration and deletes it. Returns an error if one occurs. 95 | func (c *FakeManagedProxyConfigurations) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 96 | _, err := c.Fake. 97 | Invokes(testing.NewRootDeleteActionWithOptions(managedproxyconfigurationsResource, name, opts), &v1alpha1.ManagedProxyConfiguration{}) 98 | return err 99 | } 100 | 101 | // DeleteCollection deletes a collection of objects. 102 | func (c *FakeManagedProxyConfigurations) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 103 | action := testing.NewRootDeleteCollectionAction(managedproxyconfigurationsResource, listOpts) 104 | 105 | _, err := c.Fake.Invokes(action, &v1alpha1.ManagedProxyConfigurationList{}) 106 | return err 107 | } 108 | 109 | // Patch applies the patch and returns the patched managedProxyConfiguration. 110 | func (c *FakeManagedProxyConfigurations) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ManagedProxyConfiguration, err error) { 111 | obj, err := c.Fake. 112 | Invokes(testing.NewRootPatchSubresourceAction(managedproxyconfigurationsResource, name, pt, data, subresources...), &v1alpha1.ManagedProxyConfiguration{}) 113 | if obj == nil { 114 | return nil, err 115 | } 116 | return obj.(*v1alpha1.ManagedProxyConfiguration), err 117 | } 118 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/fake/fake_managedproxyserviceresolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | "context" 7 | 8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | labels "k8s.io/apimachinery/pkg/labels" 10 | schema "k8s.io/apimachinery/pkg/runtime/schema" 11 | types "k8s.io/apimachinery/pkg/types" 12 | watch "k8s.io/apimachinery/pkg/watch" 13 | testing "k8s.io/client-go/testing" 14 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 15 | ) 16 | 17 | // FakeManagedProxyServiceResolvers implements ManagedProxyServiceResolverInterface 18 | type FakeManagedProxyServiceResolvers struct { 19 | Fake *FakeProxyV1alpha1 20 | } 21 | 22 | var managedproxyserviceresolversResource = schema.GroupVersionResource{Group: "proxy.open-cluster-management.io", Version: "v1alpha1", Resource: "managedproxyserviceresolvers"} 23 | 24 | var managedproxyserviceresolversKind = schema.GroupVersionKind{Group: "proxy.open-cluster-management.io", Version: "v1alpha1", Kind: "ManagedProxyServiceResolver"} 25 | 26 | // Get takes name of the managedProxyServiceResolver, and returns the corresponding managedProxyServiceResolver object, and an error if there is any. 27 | func (c *FakeManagedProxyServiceResolvers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ManagedProxyServiceResolver, err error) { 28 | obj, err := c.Fake. 29 | Invokes(testing.NewRootGetAction(managedproxyserviceresolversResource, name), &v1alpha1.ManagedProxyServiceResolver{}) 30 | if obj == nil { 31 | return nil, err 32 | } 33 | return obj.(*v1alpha1.ManagedProxyServiceResolver), err 34 | } 35 | 36 | // List takes label and field selectors, and returns the list of ManagedProxyServiceResolvers that match those selectors. 37 | func (c *FakeManagedProxyServiceResolvers) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ManagedProxyServiceResolverList, err error) { 38 | obj, err := c.Fake. 39 | Invokes(testing.NewRootListAction(managedproxyserviceresolversResource, managedproxyserviceresolversKind, opts), &v1alpha1.ManagedProxyServiceResolverList{}) 40 | if obj == nil { 41 | return nil, err 42 | } 43 | 44 | label, _, _ := testing.ExtractFromListOptions(opts) 45 | if label == nil { 46 | label = labels.Everything() 47 | } 48 | list := &v1alpha1.ManagedProxyServiceResolverList{ListMeta: obj.(*v1alpha1.ManagedProxyServiceResolverList).ListMeta} 49 | for _, item := range obj.(*v1alpha1.ManagedProxyServiceResolverList).Items { 50 | if label.Matches(labels.Set(item.Labels)) { 51 | list.Items = append(list.Items, item) 52 | } 53 | } 54 | return list, err 55 | } 56 | 57 | // Watch returns a watch.Interface that watches the requested managedProxyServiceResolvers. 58 | func (c *FakeManagedProxyServiceResolvers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 59 | return c.Fake. 60 | InvokesWatch(testing.NewRootWatchAction(managedproxyserviceresolversResource, opts)) 61 | } 62 | 63 | // Create takes the representation of a managedProxyServiceResolver and creates it. Returns the server's representation of the managedProxyServiceResolver, and an error, if there is any. 64 | func (c *FakeManagedProxyServiceResolvers) Create(ctx context.Context, managedProxyServiceResolver *v1alpha1.ManagedProxyServiceResolver, opts v1.CreateOptions) (result *v1alpha1.ManagedProxyServiceResolver, err error) { 65 | obj, err := c.Fake. 66 | Invokes(testing.NewRootCreateAction(managedproxyserviceresolversResource, managedProxyServiceResolver), &v1alpha1.ManagedProxyServiceResolver{}) 67 | if obj == nil { 68 | return nil, err 69 | } 70 | return obj.(*v1alpha1.ManagedProxyServiceResolver), err 71 | } 72 | 73 | // Update takes the representation of a managedProxyServiceResolver and updates it. Returns the server's representation of the managedProxyServiceResolver, and an error, if there is any. 74 | func (c *FakeManagedProxyServiceResolvers) Update(ctx context.Context, managedProxyServiceResolver *v1alpha1.ManagedProxyServiceResolver, opts v1.UpdateOptions) (result *v1alpha1.ManagedProxyServiceResolver, err error) { 75 | obj, err := c.Fake. 76 | Invokes(testing.NewRootUpdateAction(managedproxyserviceresolversResource, managedProxyServiceResolver), &v1alpha1.ManagedProxyServiceResolver{}) 77 | if obj == nil { 78 | return nil, err 79 | } 80 | return obj.(*v1alpha1.ManagedProxyServiceResolver), err 81 | } 82 | 83 | // UpdateStatus was generated because the type contains a Status member. 84 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 85 | func (c *FakeManagedProxyServiceResolvers) UpdateStatus(ctx context.Context, managedProxyServiceResolver *v1alpha1.ManagedProxyServiceResolver, opts v1.UpdateOptions) (*v1alpha1.ManagedProxyServiceResolver, error) { 86 | obj, err := c.Fake. 87 | Invokes(testing.NewRootUpdateSubresourceAction(managedproxyserviceresolversResource, "status", managedProxyServiceResolver), &v1alpha1.ManagedProxyServiceResolver{}) 88 | if obj == nil { 89 | return nil, err 90 | } 91 | return obj.(*v1alpha1.ManagedProxyServiceResolver), err 92 | } 93 | 94 | // Delete takes name of the managedProxyServiceResolver and deletes it. Returns an error if one occurs. 95 | func (c *FakeManagedProxyServiceResolvers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 96 | _, err := c.Fake. 97 | Invokes(testing.NewRootDeleteActionWithOptions(managedproxyserviceresolversResource, name, opts), &v1alpha1.ManagedProxyServiceResolver{}) 98 | return err 99 | } 100 | 101 | // DeleteCollection deletes a collection of objects. 102 | func (c *FakeManagedProxyServiceResolvers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 103 | action := testing.NewRootDeleteCollectionAction(managedproxyserviceresolversResource, listOpts) 104 | 105 | _, err := c.Fake.Invokes(action, &v1alpha1.ManagedProxyServiceResolverList{}) 106 | return err 107 | } 108 | 109 | // Patch applies the patch and returns the patched managedProxyServiceResolver. 110 | func (c *FakeManagedProxyServiceResolvers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ManagedProxyServiceResolver, err error) { 111 | obj, err := c.Fake. 112 | Invokes(testing.NewRootPatchSubresourceAction(managedproxyserviceresolversResource, name, pt, data, subresources...), &v1alpha1.ManagedProxyServiceResolver{}) 113 | if obj == nil { 114 | return nil, err 115 | } 116 | return obj.(*v1alpha1.ManagedProxyServiceResolver), err 117 | } 118 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/fake/fake_proxy_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | rest "k8s.io/client-go/rest" 7 | testing "k8s.io/client-go/testing" 8 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned/typed/proxy/v1alpha1" 9 | ) 10 | 11 | type FakeProxyV1alpha1 struct { 12 | *testing.Fake 13 | } 14 | 15 | func (c *FakeProxyV1alpha1) ManagedProxyConfigurations() v1alpha1.ManagedProxyConfigurationInterface { 16 | return &FakeManagedProxyConfigurations{c} 17 | } 18 | 19 | func (c *FakeProxyV1alpha1) ManagedProxyServiceResolvers() v1alpha1.ManagedProxyServiceResolverInterface { 20 | return &FakeManagedProxyServiceResolvers{c} 21 | } 22 | 23 | // RESTClient returns a RESTClient that is used to communicate 24 | // with API server by this client implementation. 25 | func (c *FakeProxyV1alpha1) RESTClient() rest.Interface { 26 | var ret *rest.RESTClient 27 | return ret 28 | } 29 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | type ManagedProxyConfigurationExpansion interface{} 6 | 7 | type ManagedProxyServiceResolverExpansion interface{} 8 | -------------------------------------------------------------------------------- /pkg/generated/clientset/versioned/typed/proxy/v1alpha1/proxy_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "net/http" 7 | 8 | rest "k8s.io/client-go/rest" 9 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 10 | "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned/scheme" 11 | ) 12 | 13 | type ProxyV1alpha1Interface interface { 14 | RESTClient() rest.Interface 15 | ManagedProxyConfigurationsGetter 16 | ManagedProxyServiceResolversGetter 17 | } 18 | 19 | // ProxyV1alpha1Client is used to interact with features provided by the proxy.open-cluster-management.io group. 20 | type ProxyV1alpha1Client struct { 21 | restClient rest.Interface 22 | } 23 | 24 | func (c *ProxyV1alpha1Client) ManagedProxyConfigurations() ManagedProxyConfigurationInterface { 25 | return newManagedProxyConfigurations(c) 26 | } 27 | 28 | func (c *ProxyV1alpha1Client) ManagedProxyServiceResolvers() ManagedProxyServiceResolverInterface { 29 | return newManagedProxyServiceResolvers(c) 30 | } 31 | 32 | // NewForConfig creates a new ProxyV1alpha1Client for the given config. 33 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 34 | // where httpClient was generated with rest.HTTPClientFor(c). 35 | func NewForConfig(c *rest.Config) (*ProxyV1alpha1Client, error) { 36 | config := *c 37 | if err := setConfigDefaults(&config); err != nil { 38 | return nil, err 39 | } 40 | httpClient, err := rest.HTTPClientFor(&config) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return NewForConfigAndClient(&config, httpClient) 45 | } 46 | 47 | // NewForConfigAndClient creates a new ProxyV1alpha1Client for the given config and http client. 48 | // Note the http client provided takes precedence over the configured transport values. 49 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ProxyV1alpha1Client, error) { 50 | config := *c 51 | if err := setConfigDefaults(&config); err != nil { 52 | return nil, err 53 | } 54 | client, err := rest.RESTClientForConfigAndClient(&config, h) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &ProxyV1alpha1Client{client}, nil 59 | } 60 | 61 | // NewForConfigOrDie creates a new ProxyV1alpha1Client for the given config and 62 | // panics if there is an error in the config. 63 | func NewForConfigOrDie(c *rest.Config) *ProxyV1alpha1Client { 64 | client, err := NewForConfig(c) 65 | if err != nil { 66 | panic(err) 67 | } 68 | return client 69 | } 70 | 71 | // New creates a new ProxyV1alpha1Client for the given RESTClient. 72 | func New(c rest.Interface) *ProxyV1alpha1Client { 73 | return &ProxyV1alpha1Client{c} 74 | } 75 | 76 | func setConfigDefaults(config *rest.Config) error { 77 | gv := v1alpha1.SchemeGroupVersion 78 | config.GroupVersion = &gv 79 | config.APIPath = "/apis" 80 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 81 | 82 | if config.UserAgent == "" { 83 | config.UserAgent = rest.DefaultKubernetesUserAgent() 84 | } 85 | 86 | return nil 87 | } 88 | 89 | // RESTClient returns a RESTClient that is used to communicate 90 | // with API server by this client implementation. 91 | func (c *ProxyV1alpha1Client) RESTClient() rest.Interface { 92 | if c == nil { 93 | return nil 94 | } 95 | return c.restClient 96 | } 97 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package externalversions 4 | 5 | import ( 6 | reflect "reflect" 7 | sync "sync" 8 | time "time" 9 | 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | runtime "k8s.io/apimachinery/pkg/runtime" 12 | schema "k8s.io/apimachinery/pkg/runtime/schema" 13 | cache "k8s.io/client-go/tools/cache" 14 | versioned "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 15 | internalinterfaces "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/internalinterfaces" 16 | proxy "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/proxy" 17 | ) 18 | 19 | // SharedInformerOption defines the functional option type for SharedInformerFactory. 20 | type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory 21 | 22 | type sharedInformerFactory struct { 23 | client versioned.Interface 24 | namespace string 25 | tweakListOptions internalinterfaces.TweakListOptionsFunc 26 | lock sync.Mutex 27 | defaultResync time.Duration 28 | customResync map[reflect.Type]time.Duration 29 | 30 | informers map[reflect.Type]cache.SharedIndexInformer 31 | // startedInformers is used for tracking which informers have been started. 32 | // This allows Start() to be called multiple times safely. 33 | startedInformers map[reflect.Type]bool 34 | } 35 | 36 | // WithCustomResyncConfig sets a custom resync period for the specified informer types. 37 | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { 38 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 39 | for k, v := range resyncConfig { 40 | factory.customResync[reflect.TypeOf(k)] = v 41 | } 42 | return factory 43 | } 44 | } 45 | 46 | // WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. 47 | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { 48 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 49 | factory.tweakListOptions = tweakListOptions 50 | return factory 51 | } 52 | } 53 | 54 | // WithNamespace limits the SharedInformerFactory to the specified namespace. 55 | func WithNamespace(namespace string) SharedInformerOption { 56 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 57 | factory.namespace = namespace 58 | return factory 59 | } 60 | } 61 | 62 | // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. 63 | func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { 64 | return NewSharedInformerFactoryWithOptions(client, defaultResync) 65 | } 66 | 67 | // NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. 68 | // Listers obtained via this SharedInformerFactory will be subject to the same filters 69 | // as specified here. 70 | // Deprecated: Please use NewSharedInformerFactoryWithOptions instead 71 | func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { 72 | return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) 73 | } 74 | 75 | // NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. 76 | func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { 77 | factory := &sharedInformerFactory{ 78 | client: client, 79 | namespace: v1.NamespaceAll, 80 | defaultResync: defaultResync, 81 | informers: make(map[reflect.Type]cache.SharedIndexInformer), 82 | startedInformers: make(map[reflect.Type]bool), 83 | customResync: make(map[reflect.Type]time.Duration), 84 | } 85 | 86 | // Apply all options 87 | for _, opt := range options { 88 | factory = opt(factory) 89 | } 90 | 91 | return factory 92 | } 93 | 94 | // Start initializes all requested informers. 95 | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { 96 | f.lock.Lock() 97 | defer f.lock.Unlock() 98 | 99 | for informerType, informer := range f.informers { 100 | if !f.startedInformers[informerType] { 101 | go informer.Run(stopCh) 102 | f.startedInformers[informerType] = true 103 | } 104 | } 105 | } 106 | 107 | // WaitForCacheSync waits for all started informers' cache were synced. 108 | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { 109 | informers := func() map[reflect.Type]cache.SharedIndexInformer { 110 | f.lock.Lock() 111 | defer f.lock.Unlock() 112 | 113 | informers := map[reflect.Type]cache.SharedIndexInformer{} 114 | for informerType, informer := range f.informers { 115 | if f.startedInformers[informerType] { 116 | informers[informerType] = informer 117 | } 118 | } 119 | return informers 120 | }() 121 | 122 | res := map[reflect.Type]bool{} 123 | for informType, informer := range informers { 124 | res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) 125 | } 126 | return res 127 | } 128 | 129 | // InternalInformerFor returns the SharedIndexInformer for obj using an internal 130 | // client. 131 | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { 132 | f.lock.Lock() 133 | defer f.lock.Unlock() 134 | 135 | informerType := reflect.TypeOf(obj) 136 | informer, exists := f.informers[informerType] 137 | if exists { 138 | return informer 139 | } 140 | 141 | resyncPeriod, exists := f.customResync[informerType] 142 | if !exists { 143 | resyncPeriod = f.defaultResync 144 | } 145 | 146 | informer = newFunc(f.client, resyncPeriod) 147 | f.informers[informerType] = informer 148 | 149 | return informer 150 | } 151 | 152 | // SharedInformerFactory provides shared informers for resources in all known 153 | // API group versions. 154 | type SharedInformerFactory interface { 155 | internalinterfaces.SharedInformerFactory 156 | ForResource(resource schema.GroupVersionResource) (GenericInformer, error) 157 | WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 158 | 159 | Proxy() proxy.Interface 160 | } 161 | 162 | func (f *sharedInformerFactory) Proxy() proxy.Interface { 163 | return proxy.New(f, f.namespace, f.tweakListOptions) 164 | } 165 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package externalversions 4 | 5 | import ( 6 | "fmt" 7 | 8 | schema "k8s.io/apimachinery/pkg/runtime/schema" 9 | cache "k8s.io/client-go/tools/cache" 10 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 11 | ) 12 | 13 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 14 | // sharedInformers based on type 15 | type GenericInformer interface { 16 | Informer() cache.SharedIndexInformer 17 | Lister() cache.GenericLister 18 | } 19 | 20 | type genericInformer struct { 21 | informer cache.SharedIndexInformer 22 | resource schema.GroupResource 23 | } 24 | 25 | // Informer returns the SharedIndexInformer. 26 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 27 | return f.informer 28 | } 29 | 30 | // Lister returns the GenericLister. 31 | func (f *genericInformer) Lister() cache.GenericLister { 32 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 33 | } 34 | 35 | // ForResource gives generic access to a shared informer of the matching type 36 | // TODO extend this to unknown resources with a client pool 37 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 38 | switch resource { 39 | // Group=proxy.open-cluster-management.io, Version=v1alpha1 40 | case v1alpha1.SchemeGroupVersion.WithResource("managedproxyconfigurations"): 41 | return &genericInformer{resource: resource.GroupResource(), informer: f.Proxy().V1alpha1().ManagedProxyConfigurations().Informer()}, nil 42 | case v1alpha1.SchemeGroupVersion.WithResource("managedproxyserviceresolvers"): 43 | return &genericInformer{resource: resource.GroupResource(), informer: f.Proxy().V1alpha1().ManagedProxyServiceResolvers().Informer()}, nil 44 | 45 | } 46 | 47 | return nil, fmt.Errorf("no informer found for %v", resource) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package internalinterfaces 4 | 5 | import ( 6 | time "time" 7 | 8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | cache "k8s.io/client-go/tools/cache" 11 | versioned "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 12 | ) 13 | 14 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 15 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 16 | 17 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 18 | type SharedInformerFactory interface { 19 | Start(stopCh <-chan struct{}) 20 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 21 | } 22 | 23 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 24 | type TweakListOptionsFunc func(*v1.ListOptions) 25 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/proxy/interface.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package proxy 4 | 5 | import ( 6 | internalinterfaces "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/internalinterfaces" 7 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/proxy/v1alpha1" 8 | ) 9 | 10 | // Interface provides access to each of this group's versions. 11 | type Interface interface { 12 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 13 | V1alpha1() v1alpha1.Interface 14 | } 15 | 16 | type group struct { 17 | factory internalinterfaces.SharedInformerFactory 18 | namespace string 19 | tweakListOptions internalinterfaces.TweakListOptionsFunc 20 | } 21 | 22 | // New returns a new Interface. 23 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 24 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 25 | } 26 | 27 | // V1alpha1 returns a new v1alpha1.Interface. 28 | func (g *group) V1alpha1() v1alpha1.Interface { 29 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/proxy/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | internalinterfaces "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/internalinterfaces" 7 | ) 8 | 9 | // Interface provides access to all the informers in this group version. 10 | type Interface interface { 11 | // ManagedProxyConfigurations returns a ManagedProxyConfigurationInformer. 12 | ManagedProxyConfigurations() ManagedProxyConfigurationInformer 13 | // ManagedProxyServiceResolvers returns a ManagedProxyServiceResolverInformer. 14 | ManagedProxyServiceResolvers() ManagedProxyServiceResolverInformer 15 | } 16 | 17 | type version struct { 18 | factory internalinterfaces.SharedInformerFactory 19 | namespace string 20 | tweakListOptions internalinterfaces.TweakListOptionsFunc 21 | } 22 | 23 | // New returns a new Interface. 24 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 25 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 26 | } 27 | 28 | // ManagedProxyConfigurations returns a ManagedProxyConfigurationInformer. 29 | func (v *version) ManagedProxyConfigurations() ManagedProxyConfigurationInformer { 30 | return &managedProxyConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} 31 | } 32 | 33 | // ManagedProxyServiceResolvers returns a ManagedProxyServiceResolverInformer. 34 | func (v *version) ManagedProxyServiceResolvers() ManagedProxyServiceResolverInformer { 35 | return &managedProxyServiceResolverInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} 36 | } 37 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/proxy/v1alpha1/managedproxyconfiguration.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "context" 7 | time "time" 8 | 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | watch "k8s.io/apimachinery/pkg/watch" 12 | cache "k8s.io/client-go/tools/cache" 13 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 14 | versioned "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 15 | internalinterfaces "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/internalinterfaces" 16 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/listers/proxy/v1alpha1" 17 | ) 18 | 19 | // ManagedProxyConfigurationInformer provides access to a shared informer and lister for 20 | // ManagedProxyConfigurations. 21 | type ManagedProxyConfigurationInformer interface { 22 | Informer() cache.SharedIndexInformer 23 | Lister() v1alpha1.ManagedProxyConfigurationLister 24 | } 25 | 26 | type managedProxyConfigurationInformer struct { 27 | factory internalinterfaces.SharedInformerFactory 28 | tweakListOptions internalinterfaces.TweakListOptionsFunc 29 | } 30 | 31 | // NewManagedProxyConfigurationInformer constructs a new informer for ManagedProxyConfiguration type. 32 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 33 | // one. This reduces memory footprint and number of connections to the server. 34 | func NewManagedProxyConfigurationInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 35 | return NewFilteredManagedProxyConfigurationInformer(client, resyncPeriod, indexers, nil) 36 | } 37 | 38 | // NewFilteredManagedProxyConfigurationInformer constructs a new informer for ManagedProxyConfiguration type. 39 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 40 | // one. This reduces memory footprint and number of connections to the server. 41 | func NewFilteredManagedProxyConfigurationInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 42 | return cache.NewSharedIndexInformer( 43 | &cache.ListWatch{ 44 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 45 | if tweakListOptions != nil { 46 | tweakListOptions(&options) 47 | } 48 | return client.ProxyV1alpha1().ManagedProxyConfigurations().List(context.TODO(), options) 49 | }, 50 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 51 | if tweakListOptions != nil { 52 | tweakListOptions(&options) 53 | } 54 | return client.ProxyV1alpha1().ManagedProxyConfigurations().Watch(context.TODO(), options) 55 | }, 56 | }, 57 | &proxyv1alpha1.ManagedProxyConfiguration{}, 58 | resyncPeriod, 59 | indexers, 60 | ) 61 | } 62 | 63 | func (f *managedProxyConfigurationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 64 | return NewFilteredManagedProxyConfigurationInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 65 | } 66 | 67 | func (f *managedProxyConfigurationInformer) Informer() cache.SharedIndexInformer { 68 | return f.factory.InformerFor(&proxyv1alpha1.ManagedProxyConfiguration{}, f.defaultInformer) 69 | } 70 | 71 | func (f *managedProxyConfigurationInformer) Lister() v1alpha1.ManagedProxyConfigurationLister { 72 | return v1alpha1.NewManagedProxyConfigurationLister(f.Informer().GetIndexer()) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/generated/informers/externalversions/proxy/v1alpha1/managedproxyserviceresolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "context" 7 | time "time" 8 | 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | watch "k8s.io/apimachinery/pkg/watch" 12 | cache "k8s.io/client-go/tools/cache" 13 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 14 | versioned "open-cluster-management.io/cluster-proxy/pkg/generated/clientset/versioned" 15 | internalinterfaces "open-cluster-management.io/cluster-proxy/pkg/generated/informers/externalversions/internalinterfaces" 16 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/generated/listers/proxy/v1alpha1" 17 | ) 18 | 19 | // ManagedProxyServiceResolverInformer provides access to a shared informer and lister for 20 | // ManagedProxyServiceResolvers. 21 | type ManagedProxyServiceResolverInformer interface { 22 | Informer() cache.SharedIndexInformer 23 | Lister() v1alpha1.ManagedProxyServiceResolverLister 24 | } 25 | 26 | type managedProxyServiceResolverInformer struct { 27 | factory internalinterfaces.SharedInformerFactory 28 | tweakListOptions internalinterfaces.TweakListOptionsFunc 29 | } 30 | 31 | // NewManagedProxyServiceResolverInformer constructs a new informer for ManagedProxyServiceResolver type. 32 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 33 | // one. This reduces memory footprint and number of connections to the server. 34 | func NewManagedProxyServiceResolverInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 35 | return NewFilteredManagedProxyServiceResolverInformer(client, resyncPeriod, indexers, nil) 36 | } 37 | 38 | // NewFilteredManagedProxyServiceResolverInformer constructs a new informer for ManagedProxyServiceResolver type. 39 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 40 | // one. This reduces memory footprint and number of connections to the server. 41 | func NewFilteredManagedProxyServiceResolverInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 42 | return cache.NewSharedIndexInformer( 43 | &cache.ListWatch{ 44 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 45 | if tweakListOptions != nil { 46 | tweakListOptions(&options) 47 | } 48 | return client.ProxyV1alpha1().ManagedProxyServiceResolvers().List(context.TODO(), options) 49 | }, 50 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 51 | if tweakListOptions != nil { 52 | tweakListOptions(&options) 53 | } 54 | return client.ProxyV1alpha1().ManagedProxyServiceResolvers().Watch(context.TODO(), options) 55 | }, 56 | }, 57 | &proxyv1alpha1.ManagedProxyServiceResolver{}, 58 | resyncPeriod, 59 | indexers, 60 | ) 61 | } 62 | 63 | func (f *managedProxyServiceResolverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 64 | return NewFilteredManagedProxyServiceResolverInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 65 | } 66 | 67 | func (f *managedProxyServiceResolverInformer) Informer() cache.SharedIndexInformer { 68 | return f.factory.InformerFor(&proxyv1alpha1.ManagedProxyServiceResolver{}, f.defaultInformer) 69 | } 70 | 71 | func (f *managedProxyServiceResolverInformer) Lister() v1alpha1.ManagedProxyServiceResolverLister { 72 | return v1alpha1.NewManagedProxyServiceResolverLister(f.Informer().GetIndexer()) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/generated/listers/proxy/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by lister-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | // ManagedProxyConfigurationListerExpansion allows custom methods to be added to 6 | // ManagedProxyConfigurationLister. 7 | type ManagedProxyConfigurationListerExpansion interface{} 8 | 9 | // ManagedProxyServiceResolverListerExpansion allows custom methods to be added to 10 | // ManagedProxyServiceResolverLister. 11 | type ManagedProxyServiceResolverListerExpansion interface{} 12 | -------------------------------------------------------------------------------- /pkg/generated/listers/proxy/v1alpha1/managedproxyconfiguration.go: -------------------------------------------------------------------------------- 1 | // Code generated by lister-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "k8s.io/apimachinery/pkg/api/errors" 7 | "k8s.io/apimachinery/pkg/labels" 8 | "k8s.io/client-go/tools/cache" 9 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 10 | ) 11 | 12 | // ManagedProxyConfigurationLister helps list ManagedProxyConfigurations. 13 | // All objects returned here must be treated as read-only. 14 | type ManagedProxyConfigurationLister interface { 15 | // List lists all ManagedProxyConfigurations in the indexer. 16 | // Objects returned here must be treated as read-only. 17 | List(selector labels.Selector) (ret []*v1alpha1.ManagedProxyConfiguration, err error) 18 | // Get retrieves the ManagedProxyConfiguration from the index for a given name. 19 | // Objects returned here must be treated as read-only. 20 | Get(name string) (*v1alpha1.ManagedProxyConfiguration, error) 21 | ManagedProxyConfigurationListerExpansion 22 | } 23 | 24 | // managedProxyConfigurationLister implements the ManagedProxyConfigurationLister interface. 25 | type managedProxyConfigurationLister struct { 26 | indexer cache.Indexer 27 | } 28 | 29 | // NewManagedProxyConfigurationLister returns a new ManagedProxyConfigurationLister. 30 | func NewManagedProxyConfigurationLister(indexer cache.Indexer) ManagedProxyConfigurationLister { 31 | return &managedProxyConfigurationLister{indexer: indexer} 32 | } 33 | 34 | // List lists all ManagedProxyConfigurations in the indexer. 35 | func (s *managedProxyConfigurationLister) List(selector labels.Selector) (ret []*v1alpha1.ManagedProxyConfiguration, err error) { 36 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 37 | ret = append(ret, m.(*v1alpha1.ManagedProxyConfiguration)) 38 | }) 39 | return ret, err 40 | } 41 | 42 | // Get retrieves the ManagedProxyConfiguration from the index for a given name. 43 | func (s *managedProxyConfigurationLister) Get(name string) (*v1alpha1.ManagedProxyConfiguration, error) { 44 | obj, exists, err := s.indexer.GetByKey(name) 45 | if err != nil { 46 | return nil, err 47 | } 48 | if !exists { 49 | return nil, errors.NewNotFound(v1alpha1.Resource("managedproxyconfiguration"), name) 50 | } 51 | return obj.(*v1alpha1.ManagedProxyConfiguration), nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/generated/listers/proxy/v1alpha1/managedproxyserviceresolver.go: -------------------------------------------------------------------------------- 1 | // Code generated by lister-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "k8s.io/apimachinery/pkg/api/errors" 7 | "k8s.io/apimachinery/pkg/labels" 8 | "k8s.io/client-go/tools/cache" 9 | v1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 10 | ) 11 | 12 | // ManagedProxyServiceResolverLister helps list ManagedProxyServiceResolvers. 13 | // All objects returned here must be treated as read-only. 14 | type ManagedProxyServiceResolverLister interface { 15 | // List lists all ManagedProxyServiceResolvers in the indexer. 16 | // Objects returned here must be treated as read-only. 17 | List(selector labels.Selector) (ret []*v1alpha1.ManagedProxyServiceResolver, err error) 18 | // Get retrieves the ManagedProxyServiceResolver from the index for a given name. 19 | // Objects returned here must be treated as read-only. 20 | Get(name string) (*v1alpha1.ManagedProxyServiceResolver, error) 21 | ManagedProxyServiceResolverListerExpansion 22 | } 23 | 24 | // managedProxyServiceResolverLister implements the ManagedProxyServiceResolverLister interface. 25 | type managedProxyServiceResolverLister struct { 26 | indexer cache.Indexer 27 | } 28 | 29 | // NewManagedProxyServiceResolverLister returns a new ManagedProxyServiceResolverLister. 30 | func NewManagedProxyServiceResolverLister(indexer cache.Indexer) ManagedProxyServiceResolverLister { 31 | return &managedProxyServiceResolverLister{indexer: indexer} 32 | } 33 | 34 | // List lists all ManagedProxyServiceResolvers in the indexer. 35 | func (s *managedProxyServiceResolverLister) List(selector labels.Selector) (ret []*v1alpha1.ManagedProxyServiceResolver, err error) { 36 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 37 | ret = append(ret, m.(*v1alpha1.ManagedProxyServiceResolver)) 38 | }) 39 | return ret, err 40 | } 41 | 42 | // Get retrieves the ManagedProxyServiceResolver from the index for a given name. 43 | func (s *managedProxyServiceResolverLister) Get(name string) (*v1alpha1.ManagedProxyServiceResolver, error) { 44 | obj, exists, err := s.indexer.GetByKey(name) 45 | if err != nil { 46 | return nil, err 47 | } 48 | if !exists { 49 | return nil, errors.NewNotFound(v1alpha1.Resource("managedproxyserviceresolver"), name) 50 | } 51 | return obj.(*v1alpha1.ManagedProxyServiceResolver), nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cluster-proxy-agent-only 3 | description: A Helm chart for Cluster-Proxy OCM Addon (Agent Only) 4 | type: application 5 | version: 0.4.0 6 | appVersion: 1.0.0 7 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/addon-agent-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: {{ .Values.agentDeploymentName }} 6 | annotations: 7 | {{- with .Values.agentDeploymentAnnotations }} 8 | {{ toYaml . | indent 8 }} 9 | {{- end }} 10 | spec: 11 | replicas: {{ .Values.replicas }} 12 | selector: 13 | matchLabels: 14 | open-cluster-management.io/addon: cluster-proxy 15 | proxy.open-cluster-management.io/component-name: proxy-agent 16 | template: 17 | metadata: 18 | annotations: 19 | {{- with .Values.agentDeploymentAnnotations }} 20 | {{ toYaml . | indent 8 }} 21 | {{- end }} 22 | labels: 23 | open-cluster-management.io/addon: cluster-proxy 24 | proxy.open-cluster-management.io/component-name: proxy-agent 25 | spec: 26 | serviceAccount: cluster-proxy 27 | {{- if .Values.tolerations }} 28 | tolerations: {{ toYaml .Values.tolerations | nindent 8 }} 29 | {{- end }} 30 | {{- if .Values.nodeSelector }} 31 | nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }} 32 | {{- end }} 33 | containers: 34 | - name: proxy-agent 35 | {{- $reverseResourceRequirements := reverse .Values.global.resourceRequirements }} 36 | {{- range $item := $reverseResourceRequirements }} 37 | {{- if regexMatch $item.containerIDRegex "deployments:cluster-proxy-proxy-agent:proxy-agent" }} 38 | resources: 39 | {{- toYaml $item.resources | nindent 12 }} 40 | {{- break -}} 41 | {{- end -}} 42 | {{- end }} 43 | image: {{ .Values.proxyAgentImage }} 44 | imagePullPolicy: IfNotPresent 45 | command: 46 | - /proxy-agent 47 | args: 48 | - --proxy-server-host={{ .Values.serviceEntryPoint }} 49 | - --proxy-server-port={{ .Values.serviceEntryPointPort }} 50 | - --agent-identifiers={{ .Values.agentIdentifiers }} 51 | - --ca-cert=/etc/ca/ca.crt 52 | - --agent-cert=/etc/tls/tls.crt 53 | - --agent-key=/etc/tls/tls.key 54 | {{- range .Values.additionalProxyAgentArgs }} 55 | - {{ . }} 56 | {{- end }} 57 | securityContext: 58 | allowPrivilegeEscalation: false 59 | capabilities: 60 | drop: 61 | - ALL 62 | privileged: false 63 | runAsNonRoot: true 64 | readOnlyRootFilesystem: true 65 | livenessProbe: 66 | httpGet: 67 | path: /healthz 68 | scheme: HTTP 69 | port: 8888 70 | initialDelaySeconds: 10 71 | failureThreshold: 1 72 | periodSeconds: 10 73 | env: 74 | {{- if .Values.proxyConfig.HTTP_PROXY }} 75 | - name: HTTP_PROXY 76 | value: {{ .Values.proxyConfig.HTTP_PROXY }} 77 | {{- end }} 78 | {{- if .Values.proxyConfig.HTTPS_PROXY }} 79 | - name: HTTPS_PROXY 80 | value: {{ .Values.proxyConfig.HTTPS_PROXY }} 81 | {{- end }} 82 | {{- if .Values.proxyConfig.NO_PROXY }} 83 | - name: NO_PROXY 84 | value: {{ .Values.proxyConfig.NO_PROXY }} 85 | {{- end }} 86 | - name: ROOT_CA_CERT 87 | value: "/etc/ca/ca.crt" 88 | volumeMounts: 89 | - name: ca 90 | mountPath: /etc/ca 91 | readOnly: true 92 | - name: hub 93 | mountPath: /etc/tls 94 | readOnly: true 95 | - name: addon-agent 96 | {{- $reverseResourceRequirements := reverse .Values.global.resourceRequirements }} 97 | {{- range $item := $reverseResourceRequirements }} 98 | {{- if regexMatch $item.containerIDRegex "deployments:cluster-proxy-proxy-agent:addon-agent" }} 99 | resources: 100 | {{- toYaml $item.resources | nindent 12 }} 101 | {{- break -}} 102 | {{- end -}} 103 | {{- end }} 104 | image: {{ .Values.registry }}/{{ .Values.image }}:{{ .Values.tag }} 105 | imagePullPolicy: IfNotPresent 106 | command: 107 | - /agent 108 | args: 109 | - --v=2 110 | {{- range .Values.addonAgentArgs }} 111 | - {{ . }} 112 | {{- end }} 113 | securityContext: 114 | allowPrivilegeEscalation: false 115 | capabilities: 116 | drop: 117 | - ALL 118 | privileged: false 119 | runAsNonRoot: true 120 | readOnlyRootFilesystem: true 121 | volumeMounts: 122 | - name: hub-kubeconfig 123 | mountPath: /etc/kubeconfig/ 124 | readOnly: true 125 | - name: hub 126 | mountPath: /etc/tls 127 | readOnly: true 128 | env: 129 | - name: POD_NAMESPACE 130 | valueFrom: 131 | fieldRef: 132 | fieldPath: metadata.namespace 133 | volumes: 134 | - name: ca 135 | secret: 136 | secretName: cluster-proxy-ca 137 | - name: hub 138 | secret: 139 | secretName: cluster-proxy-open-cluster-management.io-proxy-agent-signer-client-cert 140 | - name: hub-kubeconfig 141 | secret: 142 | secretName: cluster-proxy-hub-kubeconfig 143 | imagePullSecrets: 144 | {{- range .Values.proxyAgentImagePullSecrets }} 145 | - name: {{ . }} 146 | {{- end }} 147 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/addon-agent-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: cluster-proxy-addon-agent 5 | namespace: {{ .Release.Namespace }} 6 | rules: 7 | - apiGroups: 8 | - coordination.k8s.io 9 | resources: 10 | - leases 11 | verbs: 12 | - '*' 13 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/addon-agent-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: cluster-proxy-addon-agent 5 | namespace: {{ .Release.Namespace }} 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: cluster-proxy-addon-agent 10 | subjects: 11 | - kind: ServiceAccount 12 | name: cluster-proxy 13 | namespace: {{ .Release.Namespace }} 14 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/agent-client-secret.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.includeStaticProxyAgentSecret }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | namespace: {{ .Release.Namespace }} 6 | name: cluster-proxy-open-cluster-management.io-proxy-agent-signer-client-cert 7 | data: 8 | "tls.crt": {{ .Values.staticProxyAgentSecretCert }} 9 | "tls.key": {{ .Values.staticProxyAgentSecretKey }} 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/ca-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: cluster-proxy-ca 6 | data: 7 | "ca.crt": {{ .Values.base64EncodedCAData }} 8 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/cluster-service.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.enableKubeApiProxy }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | namespace: {{ .Release.Namespace }} 6 | name: {{ .Values.clusterName }} 7 | spec: 8 | type: ExternalName 9 | externalName: kubernetes.default.{{ .Values.serviceDomain }} 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.includeNamespaceCreation }} 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: {{ .Release.Namespace }} 6 | annotations: 7 | addon.open-cluster-management.io/deletion-orphan: "" 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: cluster-proxy 6 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/templates/services-to-expose.yaml: -------------------------------------------------------------------------------- 1 | {{- range .Values.servicesToExpose }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | namespace: {{ $.Release.Namespace }} 6 | name: {{ .Host }} 7 | spec: 8 | type: ExternalName 9 | externalName: {{ .ExternalName }}.{{ $.Values.serviceDomain }} 10 | --- 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /pkg/proxyagent/agent/manifests/charts/addon-agent/values.yaml: -------------------------------------------------------------------------------- 1 | clusterName: loopback 2 | 3 | agentDeploymentName: cluster-proxy-proxy-agent 4 | 5 | includeNamespaceCreation: false 6 | 7 | # Image registry 8 | registry: quay.io/open-cluster-management 9 | 10 | # Image of the cluster-gateway instances 11 | image: cluster-proxy 12 | 13 | proxyAgentImage: quay.io/open-cluster-management/cluster-proxy:latest 14 | proxyAgentImagePullSecrets: [] 15 | 16 | # Number of replicas 17 | replicas: 1 18 | 19 | spokeAddonNamespace: "open-cluster-management-cluster-proxy" 20 | additionalProxyAgentArgs: [] 21 | 22 | agentDeploymentAnnotations: {} 23 | 24 | addonAgentArgs: [] 25 | 26 | serviceEntryPoint: "" 27 | serviceEntryPointPort: 8091 28 | 29 | base64EncodedCAData: Zm9vCg== 30 | 31 | serviceDomain: "" 32 | 33 | tolerations: [] 34 | 35 | nodeSelector: {} 36 | proxyConfig: 37 | HTTP_PROXY: null 38 | HTTPS_PROXY: null 39 | NO_PROXY: null 40 | 41 | global: 42 | resourceRequirements: 43 | - containerIDRegex: ^.+:.+:.+$ 44 | resources: 45 | requests: 46 | memory: 100Mi 47 | cpu: 200m 48 | limits: 49 | memory: 200Mi 50 | cpu: 300m 51 | -------------------------------------------------------------------------------- /pkg/proxyserver/controllers/ensure_secret_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "crypto/x509" 5 | "testing" 6 | 7 | openshiftcrypto "github.com/openshift/library-go/pkg/crypto" 8 | "github.com/stretchr/testify/assert" 9 | "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 10 | "open-cluster-management.io/cluster-proxy/pkg/proxyserver/operator/authentication/selfsigned" 11 | ) 12 | 13 | func TestEnsureSecretRotation(t *testing.T) { 14 | receivingServiceNamespace := "" 15 | receivingSANs := make([]string, 0) 16 | 17 | r := &ManagedProxyConfigurationReconciler{ 18 | newCertRotatorFunc: func(namespace, name string, sans ...string) selfsigned.CertRotation { 19 | receivingServiceNamespace = namespace 20 | receivingSANs = sans 21 | return dummyRotator{} 22 | }, 23 | CAPair: &openshiftcrypto.CA{ 24 | Config: &openshiftcrypto.TLSCertificateConfig{}, 25 | }, 26 | } 27 | expectedEntrypoint := "foo" 28 | expectedServiceName := "tik" 29 | expectedNamespace := "bar" 30 | cfg := &v1alpha1.ManagedProxyConfiguration{ 31 | Spec: v1alpha1.ManagedProxyConfigurationSpec{ 32 | ProxyServer: v1alpha1.ManagedProxyConfigurationProxyServer{ 33 | InClusterServiceName: expectedServiceName, 34 | Namespace: expectedNamespace, 35 | Entrypoint: &v1alpha1.ManagedProxyConfigurationProxyServerEntrypoint{ 36 | Type: v1alpha1.EntryPointTypeHostname, 37 | Hostname: &v1alpha1.EntryPointHostname{ 38 | Value: "example.com", 39 | }, 40 | }, 41 | }, 42 | }, 43 | } 44 | err := r.ensureRotation(cfg, expectedEntrypoint) 45 | 46 | assert.NoError(t, err) 47 | assert.Equal(t, expectedNamespace, receivingServiceNamespace) 48 | assert.Equal(t, []string{ 49 | "127.0.0.1", 50 | "localhost", 51 | "foo", 52 | "tik.bar", 53 | "tik.bar.svc", 54 | "example.com", 55 | }, receivingSANs) 56 | } 57 | 58 | var _ selfsigned.CertRotation = &dummyRotator{} 59 | 60 | type dummyRotator struct{} 61 | 62 | func (d dummyRotator) EnsureTargetCertKeyPair( 63 | signingCertKeyPair *openshiftcrypto.CA, 64 | caBundleCerts []*x509.Certificate, 65 | fns ...openshiftcrypto.CertificateExtensionFunc) error { 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/proxyserver/controllers/manifests.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "strconv" 5 | 6 | appsv1 "k8s.io/api/apps/v1" 7 | corev1 "k8s.io/api/core/v1" 8 | rbacv1 "k8s.io/api/rbac/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/utils/ptr" 11 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 12 | "open-cluster-management.io/cluster-proxy/pkg/common" 13 | ) 14 | 15 | const signerSecretName = "proxy-server-ca" 16 | 17 | func newOwnerReference(config *proxyv1alpha1.ManagedProxyConfiguration) metav1.OwnerReference { 18 | return metav1.OwnerReference{ 19 | APIVersion: proxyv1alpha1.GroupVersion.String(), 20 | Kind: "ManagedProxyConfiguration", 21 | Name: config.Name, 22 | UID: config.UID, 23 | BlockOwnerDeletion: ptr.To(true), 24 | } 25 | } 26 | 27 | func newServiceAccount(config *proxyv1alpha1.ManagedProxyConfiguration) *corev1.ServiceAccount { 28 | return &corev1.ServiceAccount{ 29 | ObjectMeta: metav1.ObjectMeta{ 30 | Namespace: config.Spec.ProxyServer.Namespace, 31 | Name: common.AddonName, 32 | OwnerReferences: []metav1.OwnerReference{ 33 | newOwnerReference(config), 34 | }, 35 | }, 36 | } 37 | } 38 | 39 | func newProxySecret(config *proxyv1alpha1.ManagedProxyConfiguration, caData []byte) *corev1.Secret { 40 | return &corev1.Secret{ 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Namespace: config.Spec.ProxyServer.Namespace, 43 | Name: signerSecretName, 44 | OwnerReferences: []metav1.OwnerReference{ 45 | newOwnerReference(config), 46 | }, 47 | }, 48 | Data: map[string][]byte{ 49 | "ca.crt": caData, 50 | }, 51 | } 52 | } 53 | func newProxyService(config *proxyv1alpha1.ManagedProxyConfiguration) *corev1.Service { 54 | return &corev1.Service{ 55 | ObjectMeta: metav1.ObjectMeta{ 56 | Namespace: config.Spec.ProxyServer.Namespace, 57 | Name: config.Spec.ProxyServer.InClusterServiceName, 58 | OwnerReferences: []metav1.OwnerReference{ 59 | newOwnerReference(config), 60 | }, 61 | }, 62 | Spec: corev1.ServiceSpec{ 63 | Selector: map[string]string{ 64 | common.LabelKeyComponentName: common.ComponentNameProxyServer, 65 | }, 66 | Type: corev1.ServiceTypeClusterIP, 67 | Ports: []corev1.ServicePort{ 68 | { 69 | Name: "proxy-server", 70 | Port: 8090, 71 | }, 72 | { 73 | Name: "agent-server", 74 | Port: 8091, 75 | }, 76 | }, 77 | }, 78 | } 79 | } 80 | 81 | func newProxyServerDeployment(config *proxyv1alpha1.ManagedProxyConfiguration) *appsv1.Deployment { 82 | return &appsv1.Deployment{ 83 | ObjectMeta: metav1.ObjectMeta{ 84 | Namespace: config.Spec.ProxyServer.Namespace, 85 | Name: config.Name, 86 | OwnerReferences: []metav1.OwnerReference{ 87 | newOwnerReference(config), 88 | }, 89 | Labels: map[string]string{ 90 | common.AnnotationKeyConfigurationGeneration: strconv.Itoa(int(config.Generation)), 91 | }, 92 | }, 93 | Spec: appsv1.DeploymentSpec{ 94 | Replicas: &config.Spec.ProxyServer.Replicas, 95 | Selector: &metav1.LabelSelector{ 96 | MatchLabels: map[string]string{ 97 | common.LabelKeyComponentName: common.ComponentNameProxyServer, 98 | }, 99 | }, 100 | Strategy: appsv1.DeploymentStrategy{ 101 | Type: appsv1.RecreateDeploymentStrategyType, 102 | }, 103 | Template: corev1.PodTemplateSpec{ 104 | ObjectMeta: metav1.ObjectMeta{ 105 | Labels: map[string]string{ 106 | common.LabelKeyComponentName: common.ComponentNameProxyServer, 107 | }, 108 | }, 109 | Spec: corev1.PodSpec{ 110 | ServiceAccountName: common.AddonName, 111 | Containers: []corev1.Container{ 112 | { 113 | Name: common.ComponentNameProxyServer, 114 | Image: config.Spec.ProxyServer.Image, 115 | ImagePullPolicy: corev1.PullAlways, 116 | Command: []string{ 117 | "/proxy-server", 118 | }, 119 | Args: append([]string{ 120 | "--server-count=" + strconv.Itoa(int(config.Spec.ProxyServer.Replicas)), 121 | "--proxy-strategies=destHost", 122 | "--server-ca-cert=/etc/server-ca-pki/ca.crt", 123 | "--server-cert=/etc/server-pki/tls.crt", 124 | "--server-key=/etc/server-pki/tls.key", 125 | "--cluster-ca-cert=/etc/server-ca-pki/ca.crt", 126 | "--cluster-cert=/etc/agent-pki/tls.crt", 127 | "--cluster-key=/etc/agent-pki/tls.key", 128 | }, config.Spec.ProxyServer.AdditionalArgs...), 129 | SecurityContext: &corev1.SecurityContext{ 130 | Capabilities: &corev1.Capabilities{ 131 | Drop: []corev1.Capability{"ALL"}, 132 | }, 133 | Privileged: ptr.To(false), 134 | RunAsNonRoot: ptr.To(true), 135 | ReadOnlyRootFilesystem: ptr.To(true), 136 | AllowPrivilegeEscalation: ptr.To(false), 137 | }, 138 | VolumeMounts: []corev1.VolumeMount{ 139 | { 140 | Name: "proxy-server-ca-certs", 141 | ReadOnly: true, 142 | MountPath: "/etc/server-ca-pki/", 143 | }, 144 | { 145 | Name: "proxy-server-certs", 146 | ReadOnly: true, 147 | MountPath: "/etc/server-pki/", 148 | }, 149 | { 150 | Name: "proxy-agent-certs", 151 | ReadOnly: true, 152 | MountPath: "/etc/agent-pki/", 153 | }, 154 | }, 155 | }, 156 | }, 157 | Volumes: []corev1.Volume{ 158 | { 159 | Name: "proxy-server-ca-certs", 160 | VolumeSource: corev1.VolumeSource{ 161 | Secret: &corev1.SecretVolumeSource{ 162 | SecretName: signerSecretName, 163 | }, 164 | }, 165 | }, 166 | { 167 | Name: "proxy-server-certs", 168 | VolumeSource: corev1.VolumeSource{ 169 | Secret: &corev1.SecretVolumeSource{ 170 | SecretName: config.Spec.Authentication.Dump.Secrets.SigningProxyServerSecretName, 171 | }, 172 | }, 173 | }, 174 | { 175 | Name: "proxy-agent-certs", 176 | VolumeSource: corev1.VolumeSource{ 177 | Secret: &corev1.SecretVolumeSource{ 178 | SecretName: config.Spec.Authentication.Dump.Secrets.SigningAgentServerSecretName, 179 | }, 180 | }, 181 | }, 182 | }, 183 | NodeSelector: config.Spec.ProxyServer.NodePlacement.NodeSelector, 184 | Tolerations: config.Spec.ProxyServer.NodePlacement.Tolerations, 185 | }, 186 | }, 187 | }, 188 | } 189 | } 190 | 191 | func newProxyServerRole(config *proxyv1alpha1.ManagedProxyConfiguration) *rbacv1.Role { 192 | return &rbacv1.Role{ 193 | ObjectMeta: metav1.ObjectMeta{ 194 | Namespace: config.Spec.ProxyServer.Namespace, 195 | Name: "cluster-proxy-addon-agent:portforward", 196 | OwnerReferences: []metav1.OwnerReference{ 197 | newOwnerReference(config), 198 | }, 199 | }, 200 | Rules: []rbacv1.PolicyRule{ 201 | { 202 | APIGroups: []string{""}, 203 | Verbs: []string{"*"}, 204 | Resources: []string{"pods", "pods/portforward"}, 205 | }, 206 | }, 207 | } 208 | } 209 | 210 | func newProxyServerRoleBinding(config *proxyv1alpha1.ManagedProxyConfiguration) *rbacv1.RoleBinding { 211 | return &rbacv1.RoleBinding{ 212 | ObjectMeta: metav1.ObjectMeta{ 213 | Namespace: config.Spec.ProxyServer.Namespace, 214 | Name: "cluster-proxy-addon-agent:portforward", 215 | OwnerReferences: []metav1.OwnerReference{ 216 | newOwnerReference(config), 217 | }, 218 | }, 219 | RoleRef: rbacv1.RoleRef{ 220 | Kind: "Role", 221 | Name: "cluster-proxy-addon-agent:portforward", 222 | }, 223 | Subjects: []rbacv1.Subject{ 224 | { 225 | Kind: rbacv1.GroupKind, 226 | Name: common.SubjectGroupClusterProxy, 227 | }, 228 | }, 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /pkg/proxyserver/controllers/service_resolver_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | apierrors "k8s.io/apimachinery/pkg/api/errors" 7 | "k8s.io/apimachinery/pkg/api/meta" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/types" 10 | utilerrors "k8s.io/apimachinery/pkg/util/errors" 11 | clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2" 12 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 13 | "open-cluster-management.io/cluster-proxy/pkg/util" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | "sigs.k8s.io/controller-runtime/pkg/handler" 17 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 18 | ) 19 | 20 | var _ reconcile.Reconciler = &ServiceResolverReconciler{} 21 | 22 | type ServiceResolverReconciler struct { 23 | client.Client 24 | } 25 | 26 | func RegisterServiceResolverReconciler(mgr ctrl.Manager) error { 27 | r := &ServiceResolverReconciler{ 28 | Client: mgr.GetClient(), 29 | } 30 | return r.SetupWithManager(mgr) 31 | } 32 | 33 | func (c *ServiceResolverReconciler) SetupWithManager(mgr ctrl.Manager) error { 34 | return ctrl.NewControllerManagedBy(mgr). 35 | For(&proxyv1alpha1.ManagedProxyServiceResolver{}). 36 | Watches( 37 | &proxyv1alpha1.ManagedProxyServiceResolver{}, 38 | handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { 39 | return []reconcile.Request{ 40 | {NamespacedName: types.NamespacedName{Name: object.GetName()}}, 41 | } 42 | }), 43 | ). 44 | Watches( 45 | &clusterv1beta2.ManagedClusterSet{}, 46 | handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { 47 | var reqs []reconcile.Request 48 | // Check whether the clusterset is related with any managedproxyserviceresolver. 49 | mpsrList := &proxyv1alpha1.ManagedProxyServiceResolverList{} 50 | err := mgr.GetClient().List(context.TODO(), mpsrList, &client.ListOptions{}) 51 | if err != nil { 52 | return reqs 53 | } 54 | for _, mpsr := range mpsrList.Items { 55 | if !util.IsServiceResolverLegal(&mpsr) { 56 | continue 57 | } 58 | if mpsr.Spec.ManagedClusterSelector.ManagedClusterSet.Name == object.GetName() { 59 | req := reconcile.Request{} 60 | req.Name = mpsr.Name 61 | reqs = append(reqs, req) 62 | break 63 | } 64 | } 65 | return reqs 66 | }), 67 | ). 68 | Complete(c) 69 | } 70 | 71 | func (c *ServiceResolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 72 | log.Info("Reconciling ServiceResolver", "name", req.Name) 73 | // refreshing service resolvers status 74 | return ctrl.Result{}, c.refreshManageProxyServiceResolversStatus() 75 | } 76 | 77 | func (c *ServiceResolverReconciler) refreshManageProxyServiceResolversStatus() error { 78 | // list ManagedProxyServiceResolvers 79 | resolvers := &proxyv1alpha1.ManagedProxyServiceResolverList{} 80 | if err := c.Client.List(context.TODO(), resolvers); err != nil { 81 | return err 82 | } 83 | 84 | var errs []error 85 | 86 | // for each resolver, get it's managedclusterset 87 | for i := range resolvers.Items { 88 | resolver := resolvers.Items[i] 89 | var expectingCondition metav1.Condition 90 | editing := resolver.DeepCopy() 91 | currentCondition := meta.FindStatusCondition(editing.Status.Conditions, proxyv1alpha1.ConditionTypeServiceResolverAvaliable) 92 | 93 | // Currently, managedclusterseletor only support clusterset type, and serviceselector only support serviceRef type. 94 | if !util.IsServiceResolverLegal(&resolver) { 95 | expectingCondition = metav1.Condition{ 96 | Type: proxyv1alpha1.ConditionTypeServiceResolverAvaliable, 97 | Status: metav1.ConditionFalse, 98 | Reason: "ManagedProxyServiceResolverNotLegal", 99 | } 100 | } else { 101 | // get managedclusterset 102 | managedClusterSet := &clusterv1beta2.ManagedClusterSet{} 103 | if err := c.Client.Get(context.TODO(), types.NamespacedName{ 104 | Name: resolver.Spec.ManagedClusterSelector.ManagedClusterSet.Name, 105 | }, managedClusterSet); err != nil { 106 | if apierrors.IsNotFound(err) { 107 | expectingCondition = metav1.Condition{ 108 | Type: proxyv1alpha1.ConditionTypeServiceResolverAvaliable, 109 | Status: metav1.ConditionFalse, 110 | Reason: "ManagedClusterSetNotExisted", 111 | } 112 | } else { 113 | return err 114 | } 115 | } else { 116 | if !managedClusterSet.DeletionTimestamp.IsZero() { 117 | expectingCondition = metav1.Condition{ 118 | Type: proxyv1alpha1.ConditionTypeServiceResolverAvaliable, 119 | Status: metav1.ConditionFalse, 120 | Reason: "ManagedClusterSetDeleting", 121 | } 122 | } else { 123 | expectingCondition = metav1.Condition{ 124 | Type: proxyv1alpha1.ConditionTypeServiceResolverAvaliable, 125 | Status: metav1.ConditionTrue, 126 | Reason: "ManagedProxyServiceResolverAvaliable", 127 | } 128 | } 129 | } 130 | } 131 | 132 | // update status; now only consider one condition. 133 | if currentCondition != nil && currentCondition.Reason == expectingCondition.Reason { 134 | continue 135 | } 136 | 137 | meta.SetStatusCondition(&editing.Status.Conditions, expectingCondition) 138 | errs = append(errs, c.Client.Status().Update(context.TODO(), editing)) 139 | } 140 | 141 | return utilerrors.NewAggregate(errs) 142 | } 143 | -------------------------------------------------------------------------------- /pkg/proxyserver/operator/authentication/selfsigned/secret.go: -------------------------------------------------------------------------------- 1 | package selfsigned 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | apierrors "k8s.io/apimachinery/pkg/api/errors" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/client-go/kubernetes" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | const ( 14 | TLSCACert = "ca.crt" 15 | TLSCAKey = "ca.key" 16 | ) 17 | 18 | func DumpSecret(c client.Client, namespace, name string, caData, certData, keyData []byte) error { 19 | secret := &corev1.Secret{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Namespace: namespace, 22 | Name: name, 23 | }, 24 | Type: corev1.SecretTypeTLS, 25 | Data: map[string][]byte{ 26 | corev1.TLSCertKey: certData, 27 | corev1.TLSPrivateKeyKey: keyData, 28 | TLSCACert: caData, 29 | }, 30 | } 31 | return c.Create(context.TODO(), secret) 32 | } 33 | 34 | func DumpCASecret(c kubernetes.Interface, namespace, name string, caCertData, caKeyData []byte) (bool, error) { 35 | secret := &corev1.Secret{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Namespace: namespace, 38 | Name: name, 39 | }, 40 | Type: corev1.SecretTypeOpaque, 41 | Data: map[string][]byte{ 42 | TLSCACert: caCertData, 43 | TLSCAKey: caKeyData, 44 | }, 45 | } 46 | _, err := c.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) 47 | if apierrors.IsAlreadyExists(err) { 48 | return true, nil 49 | } 50 | return false, err 51 | } 52 | -------------------------------------------------------------------------------- /pkg/proxyserver/operator/authentication/selfsigned/self_sign_certificate.go: -------------------------------------------------------------------------------- 1 | package selfsigned 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "fmt" 12 | "math/big" 13 | "time" 14 | 15 | "open-cluster-management.io/cluster-proxy/pkg/common" 16 | 17 | openshiftcrypto "github.com/openshift/library-go/pkg/crypto" 18 | "github.com/pkg/errors" 19 | apierrors "k8s.io/apimachinery/pkg/api/errors" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "k8s.io/client-go/kubernetes" 22 | "k8s.io/client-go/util/cert" 23 | ) 24 | 25 | var ( 26 | rsaKeySize = 2048 // a decent number, as of 2019 27 | bigOne = big.NewInt(1) 28 | ) 29 | 30 | type CertRotation interface { 31 | EnsureTargetCertKeyPair(signingCertKeyPair *openshiftcrypto.CA, caBundleCerts []*x509.Certificate, fns ...openshiftcrypto.CertificateExtensionFunc) error 32 | } 33 | 34 | type SelfSigner interface { 35 | Sign(cfg cert.Config, expiry time.Duration) (CertPair, error) 36 | CAData() []byte 37 | GetSigner() crypto.Signer 38 | CA() *openshiftcrypto.CA 39 | } 40 | 41 | var _ SelfSigner = &selfSigner{} 42 | 43 | type selfSigner struct { 44 | caCert *x509.Certificate 45 | caKey crypto.Signer 46 | nextSerial *big.Int 47 | } 48 | 49 | func NewSelfSignerFromSecretOrGenerate(c kubernetes.Interface, secretNamespace, secretName string) (SelfSigner, error) { 50 | shouldGenerate := false 51 | caSecret, err := c.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 52 | if err != nil { 53 | if !apierrors.IsNotFound(err) { 54 | return nil, errors.Wrapf(err, "failed to read ca from secret %v/%v", secretNamespace, secretName) 55 | } 56 | shouldGenerate = true 57 | } 58 | if !shouldGenerate { 59 | return NewSelfSignerWithCAData(caSecret.Data[TLSCACert], caSecret.Data[TLSCAKey]) 60 | } 61 | generatedSigner, err := NewGeneratedSelfSigner() 62 | if err != nil { 63 | return nil, errors.Wrapf(err, "failed to generate new self-signer") 64 | } 65 | 66 | rawKeyData, err := x509.MarshalPKCS8PrivateKey(generatedSigner.GetSigner()) 67 | if err != nil { 68 | return nil, err 69 | } 70 | caKeyData := pem.EncodeToMemory(&pem.Block{ 71 | Type: "PRIVATE KEY", 72 | Bytes: rawKeyData, 73 | }) 74 | isAlreadyExists, err := DumpCASecret(c, 75 | secretNamespace, secretName, 76 | generatedSigner.CAData(), caKeyData) 77 | if err != nil { 78 | return nil, errors.Wrapf(err, "failed to dump generated ca secret %v/%v", secretNamespace, secretName) 79 | } 80 | if isAlreadyExists { 81 | return NewSelfSignerFromSecretOrGenerate(c, secretNamespace, secretName) 82 | } 83 | return generatedSigner, nil 84 | } 85 | 86 | func NewGeneratedSelfSigner() (SelfSigner, error) { 87 | privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) 88 | if err != nil { 89 | return nil, err 90 | } 91 | caCert, err := cert.NewSelfSignedCACert(cert.Config{ 92 | CommonName: common.AddonFullName, 93 | }, privateKey) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return NewSelfSignerWithCA(caCert, privateKey, big.NewInt(1)) 98 | } 99 | 100 | func NewSelfSignerWithCAData(caCertData, caKeyData []byte) (SelfSigner, error) { 101 | certBlock, _ := pem.Decode(caCertData) 102 | caCert, err := x509.ParseCertificate(certBlock.Bytes) 103 | if err != nil { 104 | return nil, errors.Wrapf(err, "failed to parse ca certificate") 105 | } 106 | keyBlock, _ := pem.Decode(caKeyData) 107 | caKey, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes) 108 | if err != nil { 109 | return nil, errors.Wrapf(err, "failed to parse ca key") 110 | } 111 | next := big.NewInt(0) 112 | next.Add(caCert.SerialNumber, big.NewInt(1)) 113 | return NewSelfSignerWithCA(caCert, caKey.(*rsa.PrivateKey), next) 114 | } 115 | 116 | func NewSelfSignerWithCA(caCert *x509.Certificate, caKey *rsa.PrivateKey, nextSerial *big.Int) (SelfSigner, error) { 117 | return &selfSigner{ 118 | caCert: caCert, 119 | caKey: caKey, 120 | nextSerial: nextSerial, 121 | }, nil 122 | } 123 | 124 | func (s selfSigner) Sign(cfg cert.Config, expiry time.Duration) (CertPair, error) { 125 | now := time.Now() 126 | 127 | key, err := rsa.GenerateKey(rand.Reader, rsaKeySize) 128 | if err != nil { 129 | return CertPair{}, fmt.Errorf("unable to create private key: %v", err) 130 | } 131 | 132 | serial := new(big.Int).Set(s.nextSerial) 133 | s.nextSerial.Add(s.nextSerial, bigOne) 134 | 135 | template := x509.Certificate{ 136 | Subject: pkix.Name{CommonName: cfg.CommonName, Organization: cfg.Organization}, 137 | DNSNames: cfg.AltNames.DNSNames, 138 | IPAddresses: cfg.AltNames.IPs, 139 | SerialNumber: serial, 140 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 141 | ExtKeyUsage: cfg.Usages, 142 | NotBefore: now.UTC(), 143 | NotAfter: now.Add(expiry).UTC(), 144 | } 145 | 146 | certRaw, err := x509.CreateCertificate(rand.Reader, &template, s.caCert, key.Public(), s.caKey) 147 | if err != nil { 148 | return CertPair{}, fmt.Errorf("unable to create certificate: %v", err) 149 | } 150 | 151 | certificate, err := x509.ParseCertificate(certRaw) 152 | if err != nil { 153 | return CertPair{}, fmt.Errorf("generated invalid certificate, could not parse: %v", err) 154 | } 155 | 156 | return CertPair{ 157 | Key: key, 158 | Cert: certificate, 159 | }, nil 160 | } 161 | 162 | func (s selfSigner) CAData() []byte { 163 | return pem.EncodeToMemory(&pem.Block{ 164 | Type: "CERTIFICATE", 165 | Bytes: s.caCert.Raw, 166 | }) 167 | } 168 | 169 | func (s selfSigner) GetSigner() crypto.Signer { 170 | return s.caKey 171 | } 172 | 173 | func (s selfSigner) CA() *openshiftcrypto.CA { 174 | return &openshiftcrypto.CA{ 175 | Config: &openshiftcrypto.TLSCertificateConfig{ 176 | Certs: []*x509.Certificate{s.caCert}, 177 | Key: s.caKey, 178 | }, 179 | SerialGenerator: &openshiftcrypto.RandomSerialGenerator{}, 180 | } 181 | } 182 | 183 | type CertPair struct { 184 | Key crypto.Signer 185 | Cert *x509.Certificate 186 | } 187 | 188 | func (k CertPair) CertBytes() []byte { 189 | return pem.EncodeToMemory(&pem.Block{ 190 | Type: "CERTIFICATE", 191 | Bytes: k.Cert.Raw, 192 | }) 193 | } 194 | 195 | func (k CertPair) AsBytes() (cert []byte, key []byte, err error) { 196 | cert = k.CertBytes() 197 | 198 | rawKeyData, err := x509.MarshalPKCS8PrivateKey(k.Key) 199 | if err != nil { 200 | return nil, nil, fmt.Errorf("unable to encode private key: %v", err) 201 | } 202 | 203 | key = pem.EncodeToMemory(&pem.Block{ 204 | Type: "PRIVATE KEY", 205 | Bytes: rawKeyData, 206 | }) 207 | 208 | return cert, key, nil 209 | } 210 | -------------------------------------------------------------------------------- /pkg/util/secrets.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | 8 | "github.com/pkg/errors" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 13 | ) 14 | 15 | const ( 16 | inClusterSecretProxyCA = "proxy-server-ca" 17 | inClusterSecretClient = "proxy-client" 18 | ) 19 | 20 | func GetKonnectivityTLSConfig(restConfig *rest.Config, proxyConfig *proxyv1alpha1.ManagedProxyConfiguration) (*tls.Config, error) { 21 | // building tls config from secret data 22 | nativeClient, err := kubernetes.NewForConfig(restConfig) 23 | if err != nil { 24 | return nil, errors.Wrapf(err, "failed building cilent") 25 | } 26 | caSecret, err := nativeClient.CoreV1().Secrets(proxyConfig.Spec.ProxyServer.Namespace). 27 | Get(context.TODO(), inClusterSecretProxyCA, metav1.GetOptions{}) 28 | if err != nil { 29 | return nil, errors.Wrapf(err, "failed getting CA secret") 30 | } 31 | caData := caSecret.Data["ca.crt"] 32 | certSecret, err := nativeClient.CoreV1().Secrets(proxyConfig.Spec.ProxyServer.Namespace). 33 | Get(context.TODO(), inClusterSecretClient, metav1.GetOptions{}) 34 | if err != nil { 35 | return nil, errors.Wrapf(err, "failed getting cert & key secret") 36 | } 37 | certData := certSecret.Data["tls.crt"] 38 | keyData := certSecret.Data["tls.key"] 39 | 40 | // building tls config from pem-encoded data 41 | tlsCfg, err := buildTLSConfig( 42 | caData, 43 | certData, 44 | keyData, 45 | proxyConfig.Spec.ProxyServer.InClusterServiceName+"."+proxyConfig.Spec.ProxyServer.Namespace, 46 | nil) 47 | if err != nil { 48 | return nil, errors.Wrapf(err, "failed building TLS config from secret") 49 | } 50 | return tlsCfg, nil 51 | 52 | } 53 | 54 | func buildTLSConfig(caData, certData, keyData []byte, serverName string, protos []string) (*tls.Config, error) { 55 | certPool := x509.NewCertPool() 56 | certPool.AppendCertsFromPEM(caData) 57 | clientCert, err := tls.X509KeyPair(certData, keyData) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | tlsCfg := &tls.Config{ 63 | RootCAs: certPool, 64 | MinVersion: tls.VersionTLS12, 65 | Certificates: []tls.Certificate{clientCert}, 66 | ServerName: serverName, 67 | } 68 | if len(protos) > 0 { 69 | tlsCfg.NextProtos = protos 70 | } 71 | return tlsCfg, nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/util/serviceresolver.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 5 | ) 6 | 7 | func IsServiceResolverLegal(mpsr *proxyv1alpha1.ManagedProxyServiceResolver) bool { 8 | // Check managed cluster selector 9 | if mpsr.Spec.ManagedClusterSelector.Type != proxyv1alpha1.ManagedClusterSelectorTypeClusterSet { 10 | return false 11 | } 12 | if mpsr.Spec.ManagedClusterSelector.ManagedClusterSet == nil { 13 | return false 14 | } 15 | // Check service selector 16 | if mpsr.Spec.ServiceSelector.Type != proxyv1alpha1.ServiceSelectorTypeServiceRef { 17 | return false 18 | } 19 | if mpsr.Spec.ServiceSelector.ServiceRef == nil { 20 | return false 21 | } 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /pkg/util/serviceresolver_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 7 | ) 8 | 9 | func TestIsServiceResolverLegal(t *testing.T) { 10 | testcases := []struct { 11 | name string 12 | mpsr *proxyv1alpha1.ManagedProxyServiceResolver 13 | expected bool 14 | }{ 15 | { 16 | name: "managed cluster selector type mismatch", 17 | mpsr: &proxyv1alpha1.ManagedProxyServiceResolver{}, 18 | expected: false, 19 | }, 20 | { 21 | name: "cluster set nil", 22 | mpsr: &proxyv1alpha1.ManagedProxyServiceResolver{ 23 | Spec: proxyv1alpha1.ManagedProxyServiceResolverSpec{ 24 | ManagedClusterSelector: proxyv1alpha1.ManagedClusterSelector{ 25 | Type: "Test", 26 | ManagedClusterSet: &proxyv1alpha1.ManagedClusterSet{ 27 | Name: "clusterSet1", 28 | }, 29 | }, 30 | }, 31 | }, 32 | expected: false, 33 | }, 34 | { 35 | name: "service selector type mismatch", 36 | mpsr: &proxyv1alpha1.ManagedProxyServiceResolver{ 37 | Spec: proxyv1alpha1.ManagedProxyServiceResolverSpec{ 38 | ManagedClusterSelector: proxyv1alpha1.ManagedClusterSelector{ 39 | Type: proxyv1alpha1.ManagedClusterSelectorTypeClusterSet, 40 | ManagedClusterSet: &proxyv1alpha1.ManagedClusterSet{ 41 | Name: "clusterSet1", 42 | }, 43 | }, 44 | ServiceSelector: proxyv1alpha1.ServiceSelector{ 45 | Type: "Test", 46 | ServiceRef: &proxyv1alpha1.ServiceRef{ 47 | Namespace: "ns1", 48 | Name: "service1", 49 | }, 50 | }, 51 | }, 52 | }, 53 | expected: false, 54 | }, 55 | { 56 | name: "service ref nil", 57 | mpsr: &proxyv1alpha1.ManagedProxyServiceResolver{ 58 | Spec: proxyv1alpha1.ManagedProxyServiceResolverSpec{ 59 | ManagedClusterSelector: proxyv1alpha1.ManagedClusterSelector{ 60 | Type: proxyv1alpha1.ManagedClusterSelectorTypeClusterSet, 61 | ManagedClusterSet: &proxyv1alpha1.ManagedClusterSet{ 62 | Name: "clusterSet1", 63 | }, 64 | }, 65 | ServiceSelector: proxyv1alpha1.ServiceSelector{ 66 | Type: proxyv1alpha1.ServiceSelectorTypeServiceRef, 67 | }, 68 | }, 69 | }, 70 | expected: false, 71 | }, 72 | { 73 | name: "legal", 74 | mpsr: &proxyv1alpha1.ManagedProxyServiceResolver{ 75 | Spec: proxyv1alpha1.ManagedProxyServiceResolverSpec{ 76 | ManagedClusterSelector: proxyv1alpha1.ManagedClusterSelector{ 77 | Type: proxyv1alpha1.ManagedClusterSelectorTypeClusterSet, 78 | ManagedClusterSet: &proxyv1alpha1.ManagedClusterSet{ 79 | Name: "clusterSet1", 80 | }, 81 | }, 82 | ServiceSelector: proxyv1alpha1.ServiceSelector{ 83 | Type: proxyv1alpha1.ServiceSelectorTypeServiceRef, 84 | ServiceRef: &proxyv1alpha1.ServiceRef{ 85 | Namespace: "ns1", 86 | Name: "service1", 87 | }, 88 | }, 89 | }, 90 | }, 91 | expected: true, 92 | }, 93 | } 94 | 95 | for _, tc := range testcases { 96 | t.Run(tc.name, func(t *testing.T) { 97 | actual := IsServiceResolverLegal(tc.mpsr) 98 | if actual != tc.expected { 99 | t.Errorf("expected %v, got %v", tc.expected, actual) 100 | } 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pkg/util/serviceurl.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | ) 7 | 8 | func GenerateServiceURL(cluster, namespace, service string) string { 9 | // Using hash to generate a random string; 10 | // Sum256 will give a string with length equals 64. But the name of a service must be no more than 63 characters. 11 | // Also need to add "cluster-proxy-" as prefix to prevent content starts with a number. 12 | content := sha256.Sum256([]byte(fmt.Sprintf("%s %s %s", cluster, namespace, service))) 13 | return fmt.Sprintf("cluster-proxy-%x", content)[:63] 14 | } 15 | -------------------------------------------------------------------------------- /pkg/util/serviceurl_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestGenerateServiceURL(t *testing.T) { 6 | testcases := []struct { 7 | cluster string 8 | namespace string 9 | service string 10 | expected string 11 | }{ 12 | { 13 | cluster: "cluster1", 14 | namespace: "default", 15 | service: "service1", 16 | expected: "cluster-proxy-1f28e10e03e76d3df8306b102a1da1adc79e744dd27fe48eb", 17 | }, 18 | } 19 | 20 | for _, tc := range testcases { 21 | actual := GenerateServiceURL(tc.cluster, tc.namespace, tc.service) 22 | if actual != tc.expected { 23 | t.Errorf("expected %s, got %s", tc.expected, actual) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/certificate/certificate.go: -------------------------------------------------------------------------------- 1 | package certificate 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "fmt" 8 | "time" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | 13 | certificatesv1 "k8s.io/api/certificates/v1" 14 | corev1 "k8s.io/api/core/v1" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/types" 17 | "k8s.io/client-go/kubernetes" 18 | addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" 19 | clusterapiv1 "open-cluster-management.io/api/cluster/v1" 20 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 21 | "open-cluster-management.io/cluster-proxy/pkg/common" 22 | "open-cluster-management.io/cluster-proxy/pkg/proxyagent/agent" 23 | "open-cluster-management.io/cluster-proxy/test/e2e/framework" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | ) 26 | 27 | const certificateTestBasename = "certificate" 28 | 29 | var _ = Describe("Certificate rotation Test", 30 | func() { 31 | f := framework.NewE2EFramework(certificateTestBasename) 32 | It("Agent certificate's signer should be custom signer", 33 | func() { 34 | Eventually( 35 | func() error { 36 | By("ManagedClusterAddon should be present firstly") 37 | addon := &addonapiv1alpha1.ManagedClusterAddOn{} 38 | if err := f.HubRuntimeClient().Get(context.TODO(), types.NamespacedName{ 39 | Namespace: f.TestClusterName(), 40 | Name: common.AddonName, 41 | }, addon); err != nil { 42 | return err 43 | } 44 | By("A csr with custom signer should be issued") 45 | csrList := &certificatesv1.CertificateSigningRequestList{} 46 | err := f.HubRuntimeClient().List(context.TODO(), csrList, client.MatchingLabels{ 47 | addonapiv1alpha1.AddonLabelKey: common.AddonName, 48 | clusterapiv1.ClusterNameLabelKey: f.TestClusterName(), 49 | }) 50 | if err != nil { 51 | return err 52 | } 53 | if len(csrList.Items) == 0 { 54 | return fmt.Errorf("no csr created") 55 | } 56 | exists := false 57 | for _, csr := range csrList.Items { 58 | if csr.Spec.SignerName == agent.ProxyAgentSignerName { 59 | exists = true 60 | } 61 | } 62 | Expect(exists).Should(BeTrue()) 63 | 64 | By("Agent secret should be created (after CSR approval)") 65 | agentSecret := &corev1.Secret{} 66 | err = f.HubRuntimeClient().Get(context.TODO(), types.NamespacedName{ 67 | Namespace: addon.Status.Namespace, 68 | Name: agent.AgentSecretName, 69 | }, agentSecret) 70 | if err != nil { 71 | return err 72 | } 73 | return nil 74 | }). 75 | WithTimeout(time.Minute). 76 | WithPolling(time.Second * 10). 77 | Should(Succeed()) 78 | }) 79 | 80 | It("Certificate SAN customizing should work", 81 | func() { 82 | c := f.HubRuntimeClient() 83 | proxyConfiguration := &proxyv1alpha1.ManagedProxyConfiguration{} 84 | err := c.Get(context.TODO(), types.NamespacedName{ 85 | Name: "cluster-proxy", 86 | }, proxyConfiguration) 87 | Expect(err).NotTo(HaveOccurred()) 88 | 89 | expectedSAN := "foo" 90 | proxyConfiguration.Spec.Authentication.Signer.SelfSigned = &proxyv1alpha1.AuthenticationSelfSigned{} 91 | proxyConfiguration.Spec.Authentication.Signer.SelfSigned.AdditionalSANs = []string{ 92 | expectedSAN, 93 | } 94 | err = c.Update(context.TODO(), proxyConfiguration) 95 | Expect(err).NotTo(HaveOccurred()) 96 | 97 | Eventually( 98 | func() (bool, error) { 99 | signedNames, err := extractSANsFromSecret( 100 | f.HubNativeClient(), 101 | proxyConfiguration.Spec.ProxyServer.Namespace, 102 | proxyConfiguration.Spec.Authentication.Dump.Secrets.SigningAgentServerSecretName) 103 | if err != nil { 104 | return false, err 105 | } 106 | return contains(signedNames, expectedSAN), nil 107 | }) 108 | Eventually( 109 | func() (bool, error) { 110 | signedNames, err := extractSANsFromSecret( 111 | f.HubNativeClient(), 112 | proxyConfiguration.Spec.ProxyServer.Namespace, 113 | proxyConfiguration.Spec.Authentication.Dump.Secrets.SigningProxyServerSecretName) 114 | if err != nil { 115 | return false, err 116 | } 117 | return contains(signedNames, expectedSAN), nil 118 | }) 119 | }) 120 | }) 121 | 122 | func extractSANsFromSecret(c kubernetes.Interface, namespace, name string) ([]string, error) { 123 | agentServerSecret, err := c.CoreV1(). 124 | Secrets(namespace). 125 | Get(context.TODO(), name, metav1.GetOptions{}) 126 | if err != nil { 127 | return nil, err 128 | } 129 | certData, ok := agentServerSecret.Data["tls.crt"] 130 | if !ok { 131 | return nil, nil 132 | } 133 | pemBlock, _ := pem.Decode(certData) 134 | cert, err := x509.ParseCertificate(pemBlock.Bytes) 135 | if err != nil { 136 | return nil, err 137 | } 138 | return cert.DNSNames, nil // TODO: add IP SANs 139 | } 140 | 141 | func contains(values []string, target string) bool { 142 | exists := false 143 | for _, v := range values { 144 | exists = exists || (v == target) 145 | } 146 | return exists 147 | } 148 | -------------------------------------------------------------------------------- /test/e2e/e2e.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/onsi/ginkgo/v2" 7 | logf "sigs.k8s.io/controller-runtime/pkg/log" 8 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 9 | ) 10 | 11 | func RunE2ETests(t *testing.T) { 12 | logf.SetLogger(zap.New(zap.WriteTo(ginkgo.GinkgoWriter), zap.UseDevMode(true))) 13 | ginkgo.RunSpecs(t, "ClusterProxy e2e suite") 14 | } 15 | -------------------------------------------------------------------------------- /test/e2e/e2e_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | "github.com/onsi/gomega" 9 | 10 | "open-cluster-management.io/cluster-proxy/test/e2e/framework" 11 | // per-package e2e suite 12 | 13 | _ "open-cluster-management.io/cluster-proxy/test/e2e/certificate" 14 | _ "open-cluster-management.io/cluster-proxy/test/e2e/connect" 15 | _ "open-cluster-management.io/cluster-proxy/test/e2e/install" 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | gomega.RegisterFailHandler(ginkgo.Fail) 20 | framework.ParseFlags() 21 | os.Exit(m.Run()) 22 | } 23 | 24 | func TestE2E(t *testing.T) { 25 | RunE2ETests(t) 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/framework/context.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "path/filepath" 7 | 8 | "k8s.io/klog/v2" 9 | ) 10 | 11 | var e2eContext = &E2EContext{} 12 | 13 | type E2EContext struct { 14 | HubKubeConfig string 15 | TestCluster string 16 | } 17 | 18 | func ParseFlags() { 19 | registerFlags() 20 | flag.Parse() 21 | defaultFlags() 22 | validateFlags() 23 | } 24 | 25 | func registerFlags() { 26 | flag.StringVar(&e2eContext.HubKubeConfig, 27 | "hub-kubeconfig", 28 | os.Getenv("KUBECONFIG"), 29 | "Path to kubeconfig of the hub cluster.") 30 | flag.StringVar(&e2eContext.TestCluster, 31 | "test-cluster", 32 | "", 33 | "The target cluster to run the e2e suite.") 34 | } 35 | 36 | func defaultFlags() { 37 | if len(e2eContext.HubKubeConfig) == 0 { 38 | home := os.Getenv("HOME") 39 | if len(home) > 0 { 40 | e2eContext.HubKubeConfig = filepath.Join(home, ".kube", "config") 41 | } 42 | } 43 | } 44 | 45 | func validateFlags() { 46 | if len(e2eContext.HubKubeConfig) == 0 { 47 | klog.Fatalf("--hub-kubeconfig is required") 48 | } 49 | if len(e2eContext.TestCluster) == 0 { 50 | klog.Fatalf("--test-cluster is required") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/e2e/framework/framework.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "context" 5 | 6 | apierrors "k8s.io/apimachinery/pkg/api/errors" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/types" 9 | "k8s.io/apimachinery/pkg/util/rand" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" 14 | clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2" 15 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | . "github.com/onsi/ginkgo/v2" 19 | . "github.com/onsi/gomega" 20 | ) 21 | 22 | // unique identifier of the e2e run 23 | var RunID = rand.String(6) 24 | 25 | type Framework interface { 26 | HubRESTConfig() *rest.Config 27 | TestClusterName() string 28 | 29 | HubNativeClient() kubernetes.Interface 30 | HubRuntimeClient() client.Client 31 | 32 | DeployClusterSetAndBinding(ctx context.Context, clusterset, namespace string) error 33 | } 34 | 35 | var _ Framework = &framework{} 36 | 37 | type framework struct { 38 | basename string 39 | ctx *E2EContext 40 | } 41 | 42 | func NewE2EFramework(basename string) Framework { 43 | f := &framework{ 44 | basename: basename, 45 | ctx: e2eContext, 46 | } 47 | BeforeEach(f.BeforeEach) 48 | AfterEach(f.AfterEach) 49 | return f 50 | } 51 | 52 | func (f *framework) HubRESTConfig() *rest.Config { 53 | restConfig, err := clientcmd.BuildConfigFromFlags("", f.ctx.HubKubeConfig) 54 | Expect(err).NotTo(HaveOccurred()) 55 | return restConfig 56 | } 57 | 58 | func (f *framework) HubNativeClient() kubernetes.Interface { 59 | cfg := f.HubRESTConfig() 60 | nativeClient, err := kubernetes.NewForConfig(cfg) 61 | Expect(err).NotTo(HaveOccurred()) 62 | return nativeClient 63 | } 64 | 65 | func (f *framework) HubRuntimeClient() client.Client { 66 | cfg := f.HubRESTConfig() 67 | runtimeClient, err := client.New(cfg, client.Options{ 68 | Scheme: scheme, 69 | }) 70 | Expect(err).NotTo(HaveOccurred()) 71 | return runtimeClient 72 | } 73 | 74 | func (f *framework) DeployClusterSetAndBinding(ctx context.Context, clusterset, namespace string) error { 75 | err := f.HubRuntimeClient().Create(ctx, &clusterv1beta2.ManagedClusterSet{ 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Name: clusterset, 78 | }, 79 | }) 80 | 81 | if err != nil && !apierrors.IsAlreadyExists(err) { 82 | return err 83 | } 84 | 85 | err = f.HubRuntimeClient().Create(ctx, &clusterv1beta2.ManagedClusterSetBinding{ 86 | ObjectMeta: metav1.ObjectMeta{ 87 | Name: clusterset, 88 | Namespace: namespace, 89 | }, 90 | Spec: clusterv1beta2.ManagedClusterSetBindingSpec{ 91 | ClusterSet: clusterset, 92 | }, 93 | }) 94 | if err != nil && !apierrors.IsAlreadyExists(err) { 95 | return err 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func (f *framework) TestClusterName() string { 102 | return f.ctx.TestCluster 103 | } 104 | 105 | func (f *framework) BeforeEach() { 106 | proxyConfiguration := &proxyv1alpha1.ManagedProxyConfiguration{} 107 | c := f.HubRuntimeClient() 108 | err := c.Get(context.TODO(), types.NamespacedName{ 109 | Name: "cluster-proxy", 110 | }, proxyConfiguration) 111 | if apierrors.IsNotFound(err) { 112 | By("Missing ManagedProxyConfiguration, creating one") 113 | proxyConfiguration = &proxyv1alpha1.ManagedProxyConfiguration{ 114 | ObjectMeta: metav1.ObjectMeta{ 115 | Name: "cluster-proxy", 116 | }, 117 | Spec: proxyv1alpha1.ManagedProxyConfigurationSpec{ 118 | ProxyServer: proxyv1alpha1.ManagedProxyConfigurationProxyServer{ 119 | Image: "quay.io/open-cluster-management/cluster-proxy:latest", 120 | }, 121 | ProxyAgent: proxyv1alpha1.ManagedProxyConfigurationProxyAgent{ 122 | Image: "quay.io/open-cluster-management/cluster-proxy:latest", 123 | }, 124 | }, 125 | } 126 | Expect(c.Create(context.TODO(), proxyConfiguration)).NotTo(HaveOccurred()) 127 | } 128 | Expect(err).NotTo(HaveOccurred()) 129 | 130 | err = f.DeployClusterSetAndBinding(context.TODO(), "default", "open-cluster-management-addon") 131 | Expect(err).NotTo(HaveOccurred()) 132 | 133 | // create a placement 134 | placement := &clusterv1beta1.Placement{ 135 | ObjectMeta: metav1.ObjectMeta{ 136 | Name: "default", 137 | Namespace: "open-cluster-management-addon", 138 | }, 139 | Spec: clusterv1beta1.PlacementSpec{ 140 | ClusterSets: []string{"default"}, 141 | }, 142 | } 143 | 144 | err = c.Create(context.TODO(), placement) 145 | if apierrors.IsAlreadyExists(err) { 146 | return 147 | } 148 | Expect(err).NotTo(HaveOccurred()) 149 | } 150 | 151 | func (f *framework) AfterEach() { 152 | 153 | } 154 | -------------------------------------------------------------------------------- /test/e2e/framework/scheme.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 6 | k8sscheme "k8s.io/client-go/kubernetes/scheme" 7 | addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" 8 | clusterv1 "open-cluster-management.io/api/cluster/v1" 9 | clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1" 10 | clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2" 11 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 12 | ) 13 | 14 | var scheme = runtime.NewScheme() 15 | 16 | func init() { 17 | utilruntime.Must(proxyv1alpha1.AddToScheme(scheme)) 18 | utilruntime.Must(clusterv1.Install(scheme)) 19 | utilruntime.Must(clusterv1beta2.Install(scheme)) 20 | utilruntime.Must(clusterv1beta1.Install(scheme)) 21 | utilruntime.Must(addonv1alpha1.Install(scheme)) 22 | utilruntime.Must(k8sscheme.AddToScheme(scheme)) 23 | } 24 | -------------------------------------------------------------------------------- /test/e2e/install/e2e_test.go: -------------------------------------------------------------------------------- 1 | package install 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/onsi/ginkgo/v2" 8 | "github.com/onsi/gomega" 9 | "open-cluster-management.io/cluster-proxy/test/e2e/framework" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | gomega.RegisterFailHandler(ginkgo.Fail) 14 | framework.ParseFlags() 15 | os.Exit(m.Run()) 16 | } 17 | 18 | func RunE2ETests(t *testing.T) { 19 | ginkgo.RunSpecs(t, "ClusterProxy e2e suite -- install tests") 20 | } 21 | 22 | func TestE2E(t *testing.T) { 23 | RunE2ETests(t) 24 | } 25 | -------------------------------------------------------------------------------- /test/integration/controllers/managedproxyconfiguration_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | 11 | corev1 "k8s.io/api/core/v1" 12 | "k8s.io/apimachinery/pkg/api/equality" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" 15 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 16 | "open-cluster-management.io/cluster-proxy/pkg/common" 17 | ) 18 | 19 | var _ = Describe("ManagedProxyConfigurationReconciler Test", func() { 20 | var addon *addonv1alpha1.ClusterManagementAddOn 21 | var config *proxyv1alpha1.ManagedProxyConfiguration 22 | 23 | const ( 24 | proxyServerNamespace = "open-cluster-management-proxy" 25 | configName = "cluster-proxy-config" 26 | timeout = time.Second * 30 27 | interval = time.Second * 1 28 | ) 29 | 30 | BeforeEach(func() { 31 | addon = &addonv1alpha1.ClusterManagementAddOn{ 32 | ObjectMeta: metav1.ObjectMeta{ 33 | Name: common.AddonName, 34 | }, 35 | Spec: addonv1alpha1.ClusterManagementAddOnSpec{ 36 | SupportedConfigs: []addonv1alpha1.ConfigMeta{ 37 | { 38 | ConfigGroupResource: addonv1alpha1.ConfigGroupResource{ 39 | Group: "proxy.open-cluster-management.io", 40 | Resource: "managedproxyconfigurations", 41 | }, 42 | DefaultConfig: &addonv1alpha1.ConfigReferent{ 43 | Name: configName, 44 | }, 45 | }, 46 | { 47 | ConfigGroupResource: addonv1alpha1.ConfigGroupResource{ 48 | Group: "addon.open-cluster-management.io", 49 | Resource: "addondeploymentconfigs", 50 | }, 51 | }, 52 | }, 53 | }, 54 | } 55 | 56 | config = &proxyv1alpha1.ManagedProxyConfiguration{ 57 | ObjectMeta: metav1.ObjectMeta{ 58 | Name: configName, 59 | }, 60 | Spec: proxyv1alpha1.ManagedProxyConfigurationSpec{ 61 | ProxyServer: proxyv1alpha1.ManagedProxyConfigurationProxyServer{ 62 | Image: "cluster-proxy", 63 | Namespace: proxyServerNamespace, 64 | Replicas: 3, 65 | Entrypoint: &proxyv1alpha1.ManagedProxyConfigurationProxyServerEntrypoint{ 66 | Type: proxyv1alpha1.EntryPointTypePortForward, 67 | }, 68 | }, 69 | Authentication: proxyv1alpha1.ManagedProxyConfigurationAuthentication{ 70 | Signer: proxyv1alpha1.ManagedProxyConfigurationCertificateSigner{ 71 | Type: proxyv1alpha1.SelfSigned, 72 | }, 73 | Dump: proxyv1alpha1.ManagedProxyConfigurationCertificateDump{ 74 | Secrets: proxyv1alpha1.CertificateSigningSecrets{}, 75 | }, 76 | }, 77 | ProxyAgent: proxyv1alpha1.ManagedProxyConfigurationProxyAgent{ 78 | Image: "cluster-proxy-agent", 79 | }, 80 | }, 81 | } 82 | 83 | err := ctrlClient.Create(ctx, config) 84 | Expect(err).ToNot(HaveOccurred()) 85 | 86 | err = ctrlClient.Create(ctx, addon) 87 | Expect(err).ToNot(HaveOccurred()) 88 | }) 89 | 90 | AfterEach(func() { 91 | // Add any teardown steps that needs to be executed after each test 92 | err := ctrlClient.Delete(ctx, addon) 93 | Expect(err).ToNot(HaveOccurred()) 94 | 95 | err = ctrlClient.Delete(ctx, config) 96 | Expect(err).ToNot(HaveOccurred()) 97 | }) 98 | 99 | Context("Deploy proxy server", func() { 100 | It("Should have a proxy server deployed correctly with default config", func() { 101 | // Wait for reconcile done 102 | Eventually(func() error { 103 | var err error 104 | currentConfig := &proxyv1alpha1.ManagedProxyConfiguration{} 105 | err = ctrlClient.Get(ctx, client.ObjectKeyFromObject(config), currentConfig) 106 | if err != nil { 107 | return err 108 | } 109 | for _, c := range currentConfig.Status.Conditions { 110 | if c.Type == proxyv1alpha1.ConditionTypeProxyServerDeployed && corev1.ConditionStatus(c.Status) == corev1.ConditionTrue { 111 | return nil 112 | } 113 | } 114 | return fmt.Errorf("managedproxy not ready") 115 | }, 3*timeout, 3*interval).Should(Succeed()) 116 | 117 | Eventually(func() error { 118 | _, err := kubeClient.CoreV1().Namespaces().Get(ctx, proxyServerNamespace, metav1.GetOptions{}) 119 | return err 120 | }, timeout, interval).Should(Succeed()) 121 | 122 | Eventually(func() error { 123 | _, err := kubeClient.CoreV1().Secrets(proxyServerNamespace).Get(ctx, "proxy-client", metav1.GetOptions{}) 124 | return err 125 | }, timeout, interval).Should(Succeed()) 126 | 127 | Eventually(func() error { 128 | deployment, err := kubeClient.AppsV1().Deployments(proxyServerNamespace).Get(ctx, configName, metav1.GetOptions{}) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | image := deployment.Spec.Template.Spec.Containers[0].Image 134 | if image != "cluster-proxy" { 135 | return fmt.Errorf("image is not correct, get %s", image) 136 | } 137 | 138 | replicas := *deployment.Spec.Replicas 139 | if replicas != 3 { 140 | return fmt.Errorf("replicas is not correct, get %d", replicas) 141 | } 142 | return err 143 | }, timeout, interval).Should(Succeed()) 144 | }) 145 | 146 | It("Should have a proxy server deployed correctly with node selector, toleration and replicas", func() { 147 | nodeSelector := map[string]string{"dev": "prod"} 148 | tolerations := []corev1.Toleration{ 149 | { 150 | Key: "test.io/noschedule", 151 | Operator: corev1.TolerationOpEqual, 152 | Value: "noschedule", 153 | }, 154 | } 155 | 156 | Eventually(func() error { 157 | newConfig := &proxyv1alpha1.ManagedProxyConfiguration{} 158 | 159 | err := ctrlClient.Get(ctx, client.ObjectKeyFromObject(config), newConfig) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | newConfig.Spec.ProxyServer.NodePlacement = proxyv1alpha1.NodePlacement{ 165 | NodeSelector: nodeSelector, 166 | Tolerations: tolerations, 167 | } 168 | newConfig.Spec.ProxyServer.Replicas = 1 169 | 170 | // Move update in to Eventually to avoid "the object has been modified; please apply your changes to the latest version and try again" 171 | err = ctrlClient.Update(ctx, newConfig) 172 | if err != nil { 173 | return err 174 | } 175 | 176 | deployment, err := kubeClient.AppsV1().Deployments(proxyServerNamespace).Get(ctx, configName, metav1.GetOptions{}) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | if !equality.Semantic.DeepEqual(deployment.Spec.Template.Spec.NodeSelector, nodeSelector) { 182 | return fmt.Errorf("nodeSelect is not correct, got %v", deployment.Spec.Template.Spec.NodeSelector) 183 | } 184 | if !equality.Semantic.DeepEqual(deployment.Spec.Template.Spec.Tolerations, tolerations) { 185 | return fmt.Errorf("tolerations is not correct, got %v", deployment.Spec.Template.Spec.Tolerations) 186 | } 187 | if *deployment.Spec.Replicas != 1 { 188 | return fmt.Errorf("replicas is not correct, got %d", *deployment.Spec.Replicas) 189 | } 190 | return err 191 | }, timeout, interval).Should(Succeed()) 192 | }) 193 | }) 194 | }) 195 | -------------------------------------------------------------------------------- /test/integration/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "crypto" 22 | "fmt" 23 | "os" 24 | "path/filepath" 25 | "testing" 26 | "time" 27 | 28 | . "github.com/onsi/ginkgo/v2" 29 | . "github.com/onsi/gomega" 30 | openshiftcrypto "github.com/openshift/library-go/pkg/crypto" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/client-go/informers" 33 | "k8s.io/client-go/kubernetes" 34 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 35 | "k8s.io/client-go/util/cert" 36 | addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" 37 | clusterv1beta2 "open-cluster-management.io/api/cluster/v1beta2" 38 | proxyv1alpha1 "open-cluster-management.io/cluster-proxy/pkg/apis/proxy/v1alpha1" 39 | "open-cluster-management.io/cluster-proxy/pkg/proxyserver/controllers" 40 | "open-cluster-management.io/cluster-proxy/pkg/proxyserver/operator/authentication/selfsigned" 41 | ctrl "sigs.k8s.io/controller-runtime" 42 | "sigs.k8s.io/controller-runtime/pkg/client" 43 | "sigs.k8s.io/controller-runtime/pkg/envtest" 44 | logf "sigs.k8s.io/controller-runtime/pkg/log" 45 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 46 | //+kubebuilder:scaffold:imports 47 | ) 48 | 49 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 50 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 51 | 52 | var ctrlClient client.Client 53 | var kubeClient kubernetes.Interface 54 | var testEnv *envtest.Environment 55 | var ctx context.Context 56 | var cancel context.CancelFunc 57 | 58 | type SelfSigner interface { 59 | Sign(cfg cert.Config, expiry time.Duration) (selfsigned.CertPair, error) 60 | CAData() []byte 61 | GetSigner() crypto.Signer 62 | CA() *openshiftcrypto.CA 63 | } 64 | 65 | func TestAPIs(t *testing.T) { 66 | RegisterFailHandler(Fail) 67 | 68 | RunSpecs(t, "Controller Suite") 69 | } 70 | 71 | var _ = BeforeSuite(func() { 72 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 73 | 74 | By("bootstrapping test environment") 75 | testEnv = &envtest.Environment{ 76 | CRDDirectoryPaths: []string{ 77 | filepath.Join("..", "..", "..", "hack", "crd", "addon"), 78 | filepath.Join("..", "..", "..", "hack", "crd", "bases"), 79 | filepath.Join("..", "..", "..", "hack", "crd", "cluster"), 80 | }, 81 | ErrorIfCRDPathMissing: true, 82 | } 83 | 84 | cfg, err := testEnv.Start() 85 | Expect(err).NotTo(HaveOccurred()) 86 | Expect(cfg).NotTo(BeNil()) 87 | 88 | scheme := runtime.NewScheme() 89 | err = clientgoscheme.AddToScheme(scheme) 90 | Expect(err).NotTo(HaveOccurred()) 91 | 92 | err = addonv1alpha1.AddToScheme(scheme) 93 | Expect(err).NotTo(HaveOccurred()) 94 | 95 | err = proxyv1alpha1.AddToScheme(scheme) 96 | Expect(err).NotTo(HaveOccurred()) 97 | 98 | err = clusterv1beta2.AddToScheme(scheme) 99 | Expect(err).NotTo(HaveOccurred()) 100 | 101 | kubeClient, err = kubernetes.NewForConfig(cfg) 102 | Expect(err).NotTo(HaveOccurred()) 103 | 104 | kubeInformer := informers.NewSharedInformerFactory(kubeClient, 30*time.Minute) 105 | 106 | ctx, cancel = context.WithCancel(context.TODO()) 107 | 108 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 109 | Scheme: scheme, 110 | }) 111 | Expect(err).NotTo(HaveOccurred()) 112 | 113 | ctrlClient = mgr.GetClient() 114 | 115 | selfSigner, err := selfsigned.NewSelfSignerFromSecretOrGenerate(kubeClient, "default", "test-ca") 116 | Expect(err).NotTo(HaveOccurred()) 117 | 118 | err = controllers.RegisterClusterManagementAddonReconciler(mgr, selfSigner, kubeClient, kubeInformer.Core().V1().Secrets(), true) 119 | Expect(err).NotTo(HaveOccurred()) 120 | 121 | err = controllers.RegisterServiceResolverReconciler(mgr) 122 | Expect(err).NotTo(HaveOccurred()) 123 | 124 | By("start manager") 125 | go kubeInformer.Start(ctx.Done()) 126 | go func() { 127 | if err := mgr.Start(ctx); err != nil { 128 | fmt.Printf("failed to start manager, %v\n", err) 129 | os.Exit(1) 130 | } 131 | }() 132 | }) 133 | 134 | var _ = AfterSuite(func() { 135 | By("tearing down the test environment") 136 | cancel() 137 | err := testEnv.Stop() 138 | Expect(err).NotTo(HaveOccurred()) 139 | }) 140 | -------------------------------------------------------------------------------- /test/integration/doc.go: -------------------------------------------------------------------------------- 1 | // Package integration provides integration tests for open-cluster-management cluster-proxy. 2 | package integration 3 | --------------------------------------------------------------------------------