Compare commits

...

36 Commits

Author SHA1 Message Date
Somefive eee4e07950
fix: cmd/apiserver/Dockerfile to reduce vulnerabilities (#56)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6032386
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6032386
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265
- https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6055795

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-12-13 16:53:54 +08:00
Jianbo Sun 1ecdbb4aca
Merge pull request #42 from kubevela/snyk-fix-3ea40a1c7421c1fa7752257e129041ca
[Snyk] Security upgrade alpine from 3.17 to 3.18.3
2023-11-02 17:32:32 +08:00
snyk-bot 01dd83251a
fix: cmd/apiserver/Dockerfile to reduce vulnerabilities 2023-10-26 02:32:29 +00:00
Yin Da 88d23063b9 Refactor: refactor vela-prism dockerfile for arm arch
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2023-06-06 19:11:47 +08:00
Somefive c12eae92d0
Merge pull request #38 from Somefive/feat/update-ci
Feat: update ci for build-image and helm-chart
2023-06-05 17:22:57 +08:00
Yin Da dc809e6c77 Feat: update ci for build-image and helm-chart
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2023-06-05 17:22:03 +08:00
Somefive b7e45f7f84
Merge pull request #37 from Somefive/feat/upgrade-go-version
Feat: upgrade k8s.io to 0.26
2023-04-03 11:05:19 +08:00
Yin Da cad8574718 Feat: upgrade go version
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2023-04-03 10:54:31 +08:00
Somefive 946eb7a2ce
Merge pull request #34 from Somefive/refactor/common-pkg
Refactor: move common pkg to kubevela/pkg
2023-03-28 14:11:34 +08:00
Yin Da eee3f1c2cc Refactor: move common pkg to kubevela/pkg
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2023-02-22 11:58:28 +08:00
Somefive d67ab30eb9
Merge pull request #31 from Somefive/fix/add-ignore-namespace-for-list-managed-cluster
Fix: add ignore namespace for list managed cluster
2022-11-18 16:10:54 +08:00
Somefive 8026c15cec
Merge pull request #30 from Somefive/feat/dynamic-api
Feat: add dynamic api
2022-11-18 14:52:22 +08:00
Yin Da 51eb282528 Feat: upgrade cluster-gateway dependency
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-11-18 14:28:13 +08:00
Yin Da fd6779756c Test: add test for singleton & dynamic api
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-11-17 15:42:26 +08:00
Yin Da 2aca265923 Fix: add ignore namespace for list managed cluster
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-11-17 12:01:01 +08:00
Yin Da 6e77b01d61 Feat: add dynamic api
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-11-16 12:32:55 +08:00
Somefive c431248cb0
Merge pull request #29 from Somefive/feat/add-min-request-timeout-arg
Feat: add min-request-timeout args
2022-10-24 16:58:28 +08:00
Yin Da bb878067ac Feat: add min-request-timeout args
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-10-24 16:57:57 +08:00
Somefive 42496db81b
Merge pull request #28 from Somefive/feat/upgrade-go
Feat: upgrade go version
2022-10-24 16:46:01 +08:00
Yin Da bff60a0474 Feat: upgrade go version
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-10-24 16:30:53 +08:00
Somefive 6a25625e53
Merge pull request #27 from Somefive/feat/add-request-timeout-options
Feat: add request timeout options
2022-10-21 16:38:05 +08:00
Yin Da faad0deebb Feat: add request timeout options
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-10-21 15:58:03 +08:00
Somefive 5dd2da483c
Merge pull request #26 from Somefive/feat/remove-owner-reference
Feat: remove owner reference for grafana secret
2022-10-18 16:45:11 +08:00
Yin Da 9463fc48de Feat: remove owner reference for grafana secret
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-10-18 16:29:10 +08:00
Somefive 6bf3ad33f8
Merge pull request #25 from Somefive/feat/support-cluster-control-plane-label
Fix: multicluster client panic bug
2022-09-15 15:19:49 +08:00
Yin Da c3fb485060 Fix: panic bug
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-09-15 14:36:30 +08:00
Somefive c752b9d821
Merge pull request #24 from Somefive/feat/support-cluster-control-plane-label
Feat: support cluster control plane label
2022-09-15 11:43:47 +08:00
Yin Da 0b780bccf0 Feat: support cluster control plane label
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-09-15 11:33:34 +08:00
Somefive b343320c2e
Merge pull request #22 from Somefive/fix/update-readme
Chore: update readme for grafana
2022-07-28 15:08:18 +08:00
Yin Da b2d7fec4ef Chore: update readme for grafana
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-07-28 15:05:48 +08:00
Somefive c3c6d996c2
Merge pull request #21 from Somefive/fix/create-dashboard-wo-id
Fix: delete id for dashboard request
2022-07-26 20:49:45 +08:00
Yin Da 245a784aeb Feat: delete id for dashboard request
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-07-26 20:37:45 +08:00
Somefive 6268967fe5
Merge pull request #20 from Somefive/feat/remove_acr_gh_action
Feat: remove acr image build
2022-07-26 11:30:59 +08:00
Yin Da dfe1d1910f Feat: remove acr image build
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-07-26 11:24:38 +08:00
Somefive 3cbc86010c
Merge pull request #19 from Somefive/feat/remove_acr_gh_action
Fix: update acr to kubevela.net in github action for build image
2022-07-26 11:01:42 +08:00
Yin Da bdcaaa8f3c Feat: update helm chart CI
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
2022-07-26 10:59:16 +08:00
53 changed files with 2342 additions and 1440 deletions

57
.github/workflows/build-image.yml vendored Normal file
View File

@ -0,0 +1,57 @@
name: BuildImage
on:
push:
branches:
- master
- release-*
tags:
- 'v*'
workflow_dispatch: { }
jobs:
build-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oamdev/vela-prism
ghcr.io/kubevela/oamdev/vela-prism
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Login docker.io
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
file: ./cmd/apiserver/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

67
.github/workflows/chart.yml vendored Normal file
View File

@ -0,0 +1,67 @@
name: HelmChart
on:
push:
tags:
- "v*"
workflow_dispatch: {}
jobs:
publish-charts:
env:
HELM_CHART: charts/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Get the vars
id: vars
run: |
echo ::set-output name=TAG::${GITHUB_REF#refs/tags/}
- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.4.0
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '14'
- uses: oprypin/find-latest-tag@v1
with:
repository: kubevela/workflow
releases-only: true
id: latest_tag
- name: Tag helm chart image
run: |
latest_repo_tag=${{ steps.latest_tag.outputs.tag }}
sub="."
major="$(cut -d"$sub" -f1 <<<"$latest_repo_tag")"
minor="$(cut -d"$sub" -f2 <<<"$latest_repo_tag")"
patch="0"
current_repo_tag="$major.$minor.$patch"
image_tag=${GITHUB_REF#refs/tags/}
chart_version=$latest_repo_tag
if [[ ${GITHUB_REF} == "refs/heads/main" ]]; then
image_tag=latest
chart_version=${current_repo_tag}-nightly-build
fi
sed -i "s/latest/${image_tag}/g" $HELM_CHART/values.yaml
chart_smever=${chart_version#"v"}
sed -i "s/0.1.0/$chart_smever/g" $HELM_CHART/Chart.yaml
- uses: jnwng/github-app-installation-token-action@v2
id: get_app_token
with:
appId: 340472
installationId: 38064967
privateKey: ${{ secrets.GH_KUBEVELA_APP_PRIVATE_KEY }}
- name: Sync Chart Repo
run: |
git config --global user.email "135009839+kubevela[bot]@users.noreply.github.com"
git config --global user.name "kubevela[bot]"
git clone https://x-access-token:${{ steps.get_app_token.outputs.token }}@github.com/kubevela/charts.git kubevela-charts
helm package $HELM_CHART --destination ./kubevela-charts/docs/
helm repo index --url https://kubevela.github.io/charts ./kubevela-charts/docs/
cd kubevela-charts/
git add docs/
chart_version=${GITHUB_REF#refs/tags/}
git commit -m "update vela-prism chart ${chart_version}"
git push https://x-access-token:${{ steps.get_app_token.outputs.token }}@github.com/kubevela/charts.git

View File

@ -1,117 +0,0 @@
name: HelmChart
on:
push:
tags:
- "v*"
workflow_dispatch: {}
env:
BUCKET: ${{ secrets.OSS_BUCKET }}
ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
ACCESS_KEY: ${{ secrets.OSS_ACCESS_KEY }}
ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
jobs:
publish-images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Get the vars
id: vars
run: |
echo ::set-output name=TAG::${GITHUB_REF#refs/tags/}
- name: Login ghcr.io
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login Alibaba Cloud ACR
uses: docker/login-action@v1
with:
registry: kubevela-registry.cn-hangzhou.cr.aliyuncs.com
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
with:
driver-opts: image=moby/buildkit:master
- name: Build & Pushing vela-prism for ACR
run: |
docker build --build-arg GOPROXY=https://proxy.golang.org -t kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-prism:${{ steps.vars.outputs.TAG }} -f ./cmd/apiserver/Dockerfile .
docker push kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-prism:${{ steps.vars.outputs.TAG }}
- uses: docker/build-push-action@v2
name: Build & Pushing vela-prism for Dockerhub and GHCR
with:
context: .
file: ./cmd/apiserver/Dockerfile
labels: |-
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
build-args: |
GOPROXY=https://proxy.golang.org
tags: |-
docker.io/oamdev/vela-prism:${{ steps.vars.outputs.TAG }}
ghcr.io/${{ github.repository }}/vela-prism:${{ steps.vars.outputs.TAG }}
publish-charts:
env:
HELM_CHART: charts/
LOCAL_OSS_DIRECTORY: .oss/
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@master
- name: Get the vars
id: vars
run: |
echo ::set-output name=TAG::${GITHUB_REF#refs/tags/}
- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.4.0
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '14'
- uses: oprypin/find-latest-tag@v1
with:
repository: kubevela/prism
releases-only: true
id: latest_tag
- name: Tag helm chart image
run: |
latest_repo_tag=${{ steps.latest_tag.outputs.tag }}
sub="."
major="$(cut -d"$sub" -f1 <<<"$latest_repo_tag")"
minor="$(cut -d"$sub" -f2 <<<"$latest_repo_tag")"
patch="0"
current_repo_tag="$major.$minor.$patch"
image_tag=${GITHUB_REF#refs/tags/}
chart_version=$latest_repo_tag
if [[ ${GITHUB_REF} == "refs/heads/master" ]]; then
image_tag=latest
chart_version=${current_repo_tag}-nightly-build
fi
sed -i "s/latest/${image_tag}/g" $HELM_CHART/values.yaml
chart_smever=${chart_version#"v"}
sed -i "s/0.1.0/$chart_smever/g" $HELM_CHART/Chart.yaml
- name: Install ossutil
run: wget http://gosspublic.alicdn.com/ossutil/1.7.0/ossutil64 && chmod +x ossutil64 && mv ossutil64 ossutil
- name: Configure Alibaba Cloud OSSUTIL
run: ./ossutil --config-file .ossutilconfig config -i ${ACCESS_KEY} -k ${ACCESS_KEY_SECRET} -e ${ENDPOINT} -c .ossutilconfig
- name: sync cloud to local
run: ./ossutil --config-file .ossutilconfig sync oss://$BUCKET/prism $LOCAL_OSS_DIRECTORY
- name: Package helm charts
run: |
helm package $HELM_CHART --destination $LOCAL_OSS_DIRECTORY
helm repo index --url https://$BUCKET.$ENDPOINT/prism $LOCAL_OSS_DIRECTORY
- name: sync local to cloud
run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/prism -f

View File

@ -1,86 +0,0 @@
name: PostSubmit
on:
push:
branches:
- master
workflow_dispatch: {}
env:
GO_VERSION: '1.18'
jobs:
detect-noop:
runs-on: ubuntu-20.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@v3.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
paths_ignore: '["**.md", "**.mdx", "**.png", "**.jpg"]'
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false
image-multi-arch:
runs-on: ubuntu-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
strategy:
matrix:
arch: [ amd64, arm64 ]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Build Image
run: |
IMG_TAG=latest-${{ matrix.arch }} \
OS=linux \
ARCH=${{ matrix.arch }} \
make image-apiserver
- name: Push Image
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login --username ${{ secrets.DOCKER_USER }} --password-stdin
docker push oamdev/vela-prism:latest-${{ matrix.arch }}
docker push oamdev/vela-prism:latest-${{ matrix.arch }}
image-manifest:
runs-on: ubuntu-latest
needs: [ image-multi-arch ]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Create Manifest
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login --username ${{ secrets.DOCKER_USER }} --password-stdin
docker manifest create oamdev/vela-prism:latest \
oamdev/vela-prism:latest-amd64 \
oamdev/vela-prism:latest-arm64
- name: Annotate Manifest
run: |
docker manifest annotate oamdev/vela-prism:latest \
oamdev/vela-prism:latest-amd64 --arch amd64
docker manifest annotate oamdev/vela-prism:latest \
oamdev/vela-prism:latest-arm64 --arch arm64
- name: Push Manifest
run: |
docker manifest push oamdev/vela-prism:latest

View File

@ -12,7 +12,7 @@ on:
- release-*
env:
GO_VERSION: '1.18'
GO_VERSION: '1.19'
jobs:

View File

@ -56,3 +56,99 @@ Therefore, the credential information will be secured and the user can also use
After installing vela-prism in your cluster, you can run `kubectl get vela-clusters` to view all the installed clusters.
> Notice that the vela-prism bootstrap parameter contains `--storage-namespace`, which identifies the underlying namespace for storing cluster secrets and the OCM managed cluster.
### Grafana related APIs
![PrismGrafanaArch](https://github.com/kubevela/prism/blob/master/hack/prism-grafana-arch.jpg)
#### Grafana
In vela-prism, you can store grafana access into Grafana object. The Grafana object is projected into secrets in `o11y-system` (this can be configured through `--observability-namespace` parameter).
The secret embeds the access endpoint and credential (either username/password for BasicAuth or token for BearerToken) for grafana. These will be used for communicating with Grafana APIs. Example of Grafana object is shown below.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: Grafana
metadata:
name: example
spec:
access:
username: admin
password: kubevela
endpoint: https://grafana.o11y-system:3000/
```
#### GrafanaDashboard & GrafanaDatasource
After creating the Grafana object into the control plane, you are now able to manipulate Grafana resources through Kubernetes APIs now.
Currently, vela-prism provides proxies for GrafanaDatasource and GrafanaDashboard.
Their names are constructed by two parts, its original name and the backend grafana name.
For example, you can create a new GrafanaDashboard by applying the following YAML file. This will use the Grafana object above as the access credential, and call the Grafana APIs to create a new dashboard.
You can also update or delete dashboards or datasources. The spec part of GrafanaDashboard and GrafanaDatasource are directly projected into API request body.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: GrafanaDashboard
metadata:
name: dashboard-test@example
spec:
title: New dashboard
tags: []
style: dark
timezone: browser
editable: true
hideControls: false
graphTooltip: 1
panels: []
time:
from: now-6h
to: now
timepicker:
time_options: []
refresh_intervals: []
templating:
list: []
annotations:
list: []
refresh: 5s
schemaVersion: 17
version: 0
links: []
```
Another example for GrafanaDatasource.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: GrafanaDatasource
metadata:
name: prom-test@example
spec:
access: proxy
basicAuth: false
isDefault: false
name: ExamplePrometheus
readOnly: true
type: prometheus
url: https://prometheus-server.o11y-system:9090
```
#### verse operator pattern
To operate Grafana instances in Kubernetes, there are also [Grafana operators](https://github.com/grafana-operator/grafana-operator) to help manage Grafana configurations.
Compared to operator pattern, the aggregator pattern has pros and cons.
##### Pros
- There is no data consistency problem between the CustomResource and the Grafana underlying storage.
- API requests are made in time. The response is also immediate. No need for checking CustomResource status repeatedly.
- No reconciles. No need to hold CPUs and memories as controller does.
- Easy to connect with third-party Grafana instance. For example, Grafana from cloud providers.
##### Cons
- Cannot persist data outside Grafana storage. Once grafana is broken, the CustomResource will be unavailable as well.
The main drawback for vela-prism compared to operator pattern is that it cannot persist configurations. However, this can be solved through using KubeVela application to manage those configurations.
For example, you can write KubeVela applications to hold the dashboard configurations, instead of create another separate CustomResource. With KubeVela application, leverage GitOps is also possible.

View File

@ -56,3 +56,99 @@ Therefore, the credential information will be secured and the user can also use
After installing vela-prism in your cluster, you can run `kubectl get vela-clusters` to view all the installed clusters.
> Notice that the vela-prism bootstrap parameter contains `--storage-namespace`, which identifies the underlying namespace for storing cluster secrets and the OCM managed cluster.
### Grafana related APIs
![PrismGrafanaArch](https://github.com/kubevela/prism/blob/master/hack/prism-grafana-arch.jpg)
#### Grafana
In vela-prism, you can store grafana access into Grafana object. The Grafana object is projected into secrets in `o11y-system` (this can be configured through `--observability-namespace` parameter).
The secret embeds the access endpoint and credential (either username/password for BasicAuth or token for BearerToken) for grafana. These will be used for communicating with Grafana APIs. Example of Grafana object is shown below.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: Grafana
metadata:
name: example
spec:
access:
username: admin
password: kubevela
endpoint: https://grafana.o11y-system:3000/
```
#### GrafanaDashboard & GrafanaDatasource
After creating the Grafana object into the control plane, you are now able to manipulate Grafana resources through Kubernetes APIs now.
Currently, vela-prism provides proxies for GrafanaDatasource and GrafanaDashboard.
Their names are constructed by two parts, its original name and the backend grafana name.
For example, you can create a new GrafanaDashboard by applying the following YAML file. This will use the Grafana object above as the access credential, and call the Grafana APIs to create a new dashboard.
You can also update or delete dashboards or datasources. The spec part of GrafanaDashboard and GrafanaDatasource are directly projected into API request body.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: GrafanaDashboard
metadata:
name: dashboard-test@example
spec:
title: New dashboard
tags: []
style: dark
timezone: browser
editable: true
hideControls: false
graphTooltip: 1
panels: []
time:
from: now-6h
to: now
timepicker:
time_options: []
refresh_intervals: []
templating:
list: []
annotations:
list: []
refresh: 5s
schemaVersion: 17
version: 0
links: []
```
Another example for GrafanaDatasource.
```yaml
apiVersion: o11y.prism.oam.dev/v1alpha1
kind: GrafanaDatasource
metadata:
name: prom-test@example
spec:
access: proxy
basicAuth: false
isDefault: false
name: ExamplePrometheus
readOnly: true
type: prometheus
url: https://prometheus-server.o11y-system:9090
```
#### verse operator pattern
To operate Grafana instances in Kubernetes, there are also [Grafana operators](https://github.com/grafana-operator/grafana-operator) to help manage Grafana configurations.
Compared to operator pattern, the aggregator pattern has pros and cons.
##### Pros
- There is no data consistency problem between the CustomResource and the Grafana underlying storage.
- API requests are made in time. The response is also immediate. No need for checking CustomResource status repeatedly.
- No reconciles. No need to hold CPUs and memories as controller does.
- Easy to connect with third-party Grafana instance. For example, Grafana from cloud providers.
##### Cons
- Cannot persist data outside Grafana storage. Once grafana is broken, the CustomResource will be unavailable as well.
The main drawback for vela-prism compared to operator pattern is that it cannot persist configurations. However, this can be solved through using KubeVela application to manage those configurations.
For example, you can write KubeVela applications to hold the dashboard configurations, instead of create another separate CustomResource. With KubeVela application, leverage GitOps is also possible.

View File

@ -3,7 +3,7 @@ kind: APIService
metadata:
name: v1alpha1.prism.oam.dev
labels:
api: kuebvela-vela-prism
api: kubevela-vela-prism
apiserver: "true"
{{- include "vela-prism.labels" . | nindent 4 }}
spec:
@ -25,7 +25,7 @@ kind: APIService
metadata:
name: v1alpha1.o11y.prism.oam.dev
labels:
api: kuebvela-vela-prism
api: kubevela-vela-prism
apiserver: "true"
{{- include "vela-prism.labels" . | nindent 4 }}
spec:
@ -40,4 +40,28 @@ spec:
insecureSkipTLSVerify: {{ not .Values.secureTLS.enabled }}
{{ if .Values.secureTLS.enabled }}
caBundle: Cg==
{{ end }}
{{ end }}
---
{{ if .Values.dynamicAPI.enabled }}
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1alpha1.dynamic.prism.oam.dev
labels:
api: kubevela-vela-prism
apiserver: "true"
{{- include "vela-prism.labels" . | nindent 4 }}
spec:
version: v1alpha1
group: dynamic.prism.oam.dev
groupPriorityMinimum: 2000
service:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
port: {{ .Values.port }}
versionPriority: 10
insecureSkipTLSVerify: {{ not .Values.secureTLS.enabled }}
{{ if .Values.secureTLS.enabled }}
caBundle: Cg==
{{ end }}
{{ end }}

View File

@ -173,6 +173,17 @@ spec:
- --secret-namespace={{ .Release.Namespace }}
- --secret-name={{ .Release.Name }}
- --target-APIService=v1alpha1.o11y.prism.oam.dev
{{ if .Values.dynamicAPI.enabled }}
- name: patch-dynamic
image: {{ .Values.imageRegistry }}{{ .Values.secureTLS.certPatch.image.repository }}:{{ .Values.secureTLS.certPatch.image.tag }}
imagePullPolicy: {{ .Values.secureTLS.certPatch.image.pullPolicy }}
command:
- /patch
args:
- --secret-namespace={{ .Release.Namespace }}
- --secret-name={{ .Release.Name }}
- --target-APIService=v1alpha1.dynamic.prism.oam.dev
{{ end }}
restartPolicy: OnFailure
serviceAccountName: {{ .Release.Name }}-certpatch
securityContext:

View File

@ -34,6 +34,11 @@ rules:
- apiGroups: ["cluster.open-cluster-management.io"]
resources: ["managedclusters"]
verbs: ["get", "watch", "list"]
{{ if .Values.dynamicAPI.enabled }}
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "create", "patch", "update", "delete", "watch"]
{{ end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding

View File

@ -41,4 +41,7 @@ secureTLS:
image:
repository: oamdev/cluster-gateway
tag: v1.4.0
pullPolicy: IfNotPresent
pullPolicy: IfNotPresent
dynamicAPI:
enabled: true

View File

@ -1,8 +1,9 @@
ARG BASE_IMAGE
# Build the manager binary
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.18-alpine as builder
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-https://goproxy.cn}
FROM golang:1.19-alpine as builder
ARG OS
ARG ARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
@ -15,31 +16,20 @@ RUN go mod download
COPY cmd/apiserver/main.go cmd/apiserver/main.go
COPY pkg/ pkg/
# Build
ARG TARGETARCH
RUN CGO_ENABLED=0 \
GOOS=${OS} \
GOARCH=${ARCH} \
go build \
-a -ldflags "-s -w" \
-o vela-prism \
cmd/apiserver/main.go
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
go build -a -ldflags "-s -w" \
-o vela-prism-${TARGETARCH} cmd/apiserver/main.go
# Before copying the Go binary directly to the final image,
# add them to the intermdediate upx image
FROM gruebel/upx:latest as upx
ARG TARGETARCH
COPY --from=builder /workspace/vela-prism-${TARGETARCH} /workspace/vela-prism-${TARGETARCH}
# Compress the binary and copy it to final image
RUN upx --best --lzma -o /workspace/vela-prism-${TARGETARCH}-upx /workspace/vela-prism-${TARGETARCH}
# Overwrite `BASE_IMAGE` by passing `--build-arg=BASE_IMAGE=gcr.io/distroless/static:nonroot`
FROM ${BASE_IMAGE:-alpine:3.15}
FROM alpine:3.21.0
# This is required by daemon connnecting with cri
RUN apk add --no-cache ca-certificates bash expat
RUN apk add curl
WORKDIR /
ARG TARGETARCH
COPY --from=upx /workspace/vela-prism-${TARGETARCH}-upx /usr/local/bin/vela-prism
COPY --from=builder /workspace/vela-prism /usr/local/bin/vela-prism
CMD ["vela-prism"]

View File

@ -17,17 +17,21 @@ limitations under the License.
package main
import (
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/runtime"
"sigs.k8s.io/apiserver-runtime/pkg/builder"
cueserver "github.com/kubevela/pkg/cue/server"
apiserveroptions "github.com/kubevela/pkg/util/apiserver/options"
"github.com/kubevela/pkg/util/log"
"github.com/kubevela/pkg/util/singleton"
apprtv1alpha1 "github.com/kubevela/prism/pkg/apis/applicationresourcetracker/v1alpha1"
clusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
o11yconfig "github.com/kubevela/prism/pkg/apis/o11y/config"
grafanav1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafana/v1alpha1"
grafanadashboardv1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafanadashboard/v1alpha1"
grafanadatasourcev1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafanadatasource/v1alpha1"
"github.com/kubevela/prism/pkg/util/log"
"github.com/kubevela/prism/pkg/util/singleton"
apiserver "github.com/kubevela/prism/pkg/dynamicapiserver"
)
func main() {
@ -41,15 +45,14 @@ func main() {
WithResource(&grafanav1alpha1.Grafana{}).
WithResource(&grafanadatasourcev1alpha1.GrafanaDatasource{}).
WithResource(&grafanadashboardv1alpha1.GrafanaDashboard{}).
WithPostStartHook("init-master-loopback-client", singleton.InitLoopbackClient).
WithConfigFns(apiserveroptions.WrapConfig, singleton.InitServerConfig).
WithServerFns(cueserver.RegisterGenericAPIServer, singleton.InitGenericAPIServer).
WithPostStartHook("start-dynamic-server", apiserver.StartDefaultDynamicAPIServer).
Build()
if err != nil {
klog.Fatal(err)
}
runtime.Must(err)
log.AddLogFlags(cmd)
apiserveroptions.AddServerRunFlags(cmd.Flags())
clusterv1alpha1.AddClusterFlags(cmd.Flags())
o11yconfig.AddObservabilityFlags(cmd.Flags())
if err = cmd.Execute(); err != nil {
klog.Fatal(err)
}
runtime.Must(cmd.Execute())
}

174
go.mod
View File

@ -1,120 +1,134 @@
module github.com/kubevela/prism
go 1.18
go 1.19
require (
github.com/oam-dev/cluster-gateway v1.4.0
github.com/onsi/ginkgo/v2 v2.1.4
github.com/onsi/gomega v1.19.0
github.com/spf13/cobra v1.4.0
cuelang.org/go v0.5.0-beta.2.0.20230130095913-d573e0c2f041
github.com/emicklei/go-restful/v3 v3.9.0
github.com/kubevela/pkg v1.8.1-0.20230403024929-46ddc1466157
github.com/oam-dev/cluster-gateway v1.9.0-alpha.1
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
k8s.io/api v0.23.6
k8s.io/apimachinery v0.23.6
k8s.io/apiserver v0.23.6
k8s.io/client-go v0.23.6
k8s.io/klog/v2 v2.60.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
github.com/stretchr/testify v1.8.0
golang.org/x/exp v0.0.0-20221114191408-850992195362
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/apiserver v0.26.3
k8s.io/client-go v0.26.3
k8s.io/klog/v2 v2.80.1
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448
open-cluster-management.io/api v0.7.0
sigs.k8s.io/apiserver-runtime v1.1.1
sigs.k8s.io/controller-runtime v0.11.2
sigs.k8s.io/controller-tools v0.6.2
sigs.k8s.io/apiserver-runtime v1.1.2-0.20221118041430-0a6394f6dda3
sigs.k8s.io/controller-runtime v0.14.5
sigs.k8s.io/controller-tools v0.11.3
)
require (
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fatih/color v1.12.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-logr/zapr v1.2.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gobuffalo/flect v0.2.3 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobuffalo/flect v0.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/cel-go v0.12.6 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/openshift/library-go v0.0.0-20220112153822-ac82336bd076 // indirect
github.com/openshift/library-go v0.0.0-20230327085348-8477ec72b725 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.28.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
go.opentelemetry.io/contrib v0.20.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect
go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
go.opentelemetry.io/otel/trace v0.20.0 // indirect
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.5 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
go.etcd.io/etcd/client/v3 v3.5.5 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect
go.opentelemetry.io/otel v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect
go.opentelemetry.io/otel/metric v0.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.10.0 // indirect
go.opentelemetry.io/otel/trace v1.10.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
google.golang.org/grpc v1.42.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiextensions-apiserver v0.23.5 // indirect
k8s.io/component-base v0.23.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.26.3 // indirect
k8s.io/component-base v0.26.3 // indirect
k8s.io/klog v1.0.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/kms v0.26.3 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
sigs.k8s.io/apiserver-network-proxy v0.0.30 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.24
replace (
cloud.google.com/go => cloud.google.com/go v0.100.2
sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36
)

996
go.sum

File diff suppressed because it is too large Load Diff

BIN
hack/prism-grafana-arch.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -31,7 +31,7 @@ import (
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/singleton"
)
// ApplicationResourceTracker is an extension model for ResourceTracker
@ -69,6 +69,9 @@ func (in *ApplicationResourceTracker) New() runtime.Object {
return &ApplicationResourceTracker{}
}
// Destroy .
func (in *ApplicationResourceTracker) Destroy() {}
// NewList return a new list instance of the resource
func (in *ApplicationResourceTracker) NewList() runtime.Object {
return &ApplicationResourceTrackerList{}
@ -94,7 +97,7 @@ func (in *ApplicationResourceTracker) Get(ctx context.Context, name string, opti
rt := &unstructured.Unstructured{}
rt.SetGroupVersionKind(ResourceTrackerGroupVersionKind)
ns := request.NamespaceValue(ctx)
if err := singleton.GetKubeClient().Get(ctx, types.NamespacedName{Name: name + "-" + ns}, rt); err != nil {
if err := singleton.KubeClient.Get().Get(ctx, types.NamespacedName{Name: name + "-" + ns}, rt); err != nil {
return nil, err
}
return NewApplicationResourceTrackerFromResourceTracker(rt)
@ -109,7 +112,7 @@ func (in *ApplicationResourceTracker) List(ctx context.Context, options *metaint
if options != nil {
sel.selector = options.LabelSelector
}
if err := singleton.GetKubeClient().List(ctx, rts, sel); err != nil {
if err := singleton.KubeClient.Get().List(ctx, rts, sel); err != nil {
return nil, err
}
appRts := &ApplicationResourceTrackerList{}

View File

@ -27,7 +27,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/singleton"
)
var _ = Describe("Test ApplicationResourceTracker API", func() {
@ -55,7 +55,7 @@ var _ = Describe("Test ApplicationResourceTracker API", func() {
labelAppNamespace: ns,
"key": val,
})
Ω(singleton.GetKubeClient().Create(ctx, rt)).To(Succeed())
Ω(singleton.KubeClient.Get().Create(ctx, rt)).To(Succeed())
return rt
}
createRt("app-1", "example", "x")

View File

@ -29,10 +29,11 @@ import (
"k8s.io/apimachinery/pkg/selection"
apitypes "k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/utils/strings/slices"
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/pkg/util/apiserver"
)
// ClusterClient client for reading cluster information
@ -94,12 +95,8 @@ func (c *clusterClient) Get(ctx context.Context, name string) (*Cluster, error)
func (c *clusterClient) List(ctx context.Context, options ...client.ListOption) (*ClusterList, error) {
opts := apiserver.NewListOptions(options...)
clusters := &ClusterList{}
if opts.LabelSelector == nil || opts.LabelSelector.Empty() {
opts.LabelSelector = labels.NewSelector()
local := NewLocalCluster()
clusters.Items = []Cluster{*local}
}
local := NewLocalCluster()
clusters := &ClusterList{Items: []Cluster{*local}}
secrets := &corev1.SecretList{}
err := c.Client.List(ctx, secrets, clusterSelector{Selector: opts.LabelSelector, RequireCredentialType: true})
@ -113,7 +110,7 @@ func (c *clusterClient) List(ctx context.Context, options ...client.ListOption)
}
managedClusters := &ocmclusterv1.ManagedClusterList{}
err = c.Client.List(ctx, managedClusters, clusterSelector{Selector: opts.LabelSelector, RequireCredentialType: false})
err = c.Client.List(ctx, managedClusters, clusterSelector{Selector: opts.LabelSelector, RequireCredentialType: false, IgnoreNamespace: true})
if err != nil && !meta.IsNoMatchError(err) && !runtime.IsNotRegisteredError(err) {
return nil, err
}
@ -124,6 +121,17 @@ func (c *clusterClient) List(ctx context.Context, options ...client.ListOption)
}
}
}
// filter clusters
var items []Cluster
for _, cluster := range clusters.Items {
if opts.LabelSelector == nil || opts.LabelSelector.Matches(labels.Set(cluster.GetLabels())) {
items = append(items, cluster)
}
}
clusters.Items = items
// sort clusters
sort.Slice(clusters.Items, func(i, j int) bool {
if clusters.Items[i].Name == ClusterLocalName {
return true
@ -140,14 +148,25 @@ func (c *clusterClient) List(ctx context.Context, options ...client.ListOption)
type clusterSelector struct {
Selector labels.Selector
RequireCredentialType bool
IgnoreNamespace bool
}
// ApplyToList applies this configuration to the given list options.
func (m clusterSelector) ApplyToList(opts *client.ListOptions) {
opts.LabelSelector = m.Selector
opts.LabelSelector = labels.NewSelector()
if m.Selector != nil {
requirements, _ := m.Selector.Requirements()
for _, r := range requirements {
if !slices.Contains([]string{LabelClusterControlPlane}, r.Key()) {
opts.LabelSelector = opts.LabelSelector.Add(r)
}
}
}
if m.RequireCredentialType {
r, _ := labels.NewRequirement(clustergatewaycommon.LabelKeyClusterCredentialType, selection.Exists, nil)
opts.LabelSelector = opts.LabelSelector.Add(*r)
}
opts.Namespace = StorageNamespace
if !m.IgnoreNamespace {
opts.Namespace = StorageNamespace
}
}

View File

@ -36,6 +36,9 @@ const (
var (
// AnnotationClusterAlias the annotation key for cluster alias
AnnotationClusterAlias = config.MetaApiGroupName + "/cluster-alias"
// LabelClusterControlPlane identifies whether the cluster is the control plane
LabelClusterControlPlane = config.MetaApiGroupName + "/control-plane"
)
// StorageNamespace refers to the namespace of cluster secret, usually same as the core kubevela system namespace

View File

@ -18,6 +18,7 @@ package v1alpha1
import (
"context"
"fmt"
"strings"
clustergatewayv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
@ -30,18 +31,18 @@ import (
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/pkg/util/singleton"
)
// Get finds a resource in the storage by name and returns it.
func (in *Cluster) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return NewClusterClient(singleton.GetKubeClient()).Get(ctx, name)
return NewClusterClient(singleton.KubeClient.Get()).Get(ctx, name)
}
// List selects resources in the storage which match to the selector. 'options' can be nil.
func (in *Cluster) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
return NewClusterClient(singleton.GetKubeClient()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
return NewClusterClient(singleton.KubeClient.Get()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
}
func extractLabels(labels map[string]string) map[string]string {
@ -67,6 +68,7 @@ func newCluster(obj client.Object) *Cluster {
}
cluster.Spec.Accepted = true
cluster.Spec.Endpoint = ClusterBlankEndpoint
metav1.SetMetaDataLabel(&cluster.ObjectMeta, LabelClusterControlPlane, fmt.Sprintf("%t", obj == nil))
return cluster
}

View File

@ -69,6 +69,9 @@ func (in *Cluster) New() runtime.Object {
return &Cluster{}
}
// Destroy .
func (in *Cluster) Destroy() {}
// NewList return a new list instance of the resource
func (in *Cluster) NewList() runtime.Object {
return &ClusterList{}

View File

@ -30,7 +30,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/singleton"
)
var _ = Describe("Test Cluster API", func() {
@ -58,10 +58,10 @@ var _ = Describe("Test Cluster API", func() {
ctx := context.Background()
By("Create storage namespace")
Ω(singleton.GetKubeClient().Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: StorageNamespace}})).To(Succeed())
Ω(singleton.KubeClient.Get().Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: StorageNamespace}})).To(Succeed())
By("Create cluster secret")
Ω(singleton.GetKubeClient().Create(ctx, &v1.Secret{
Ω(singleton.KubeClient.Get().Create(ctx, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: StorageNamespace,
@ -73,14 +73,14 @@ var _ = Describe("Test Cluster API", func() {
Annotations: map[string]string{AnnotationClusterAlias: "test-cluster-alias"},
},
})).To(Succeed())
Ω(singleton.GetKubeClient().Create(ctx, &v1.Secret{
Ω(singleton.KubeClient.Get().Create(ctx, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-invalid",
Namespace: StorageNamespace,
},
Data: map[string][]byte{"endpoint": []byte("127.0.0.1:6443")},
})).To(Succeed())
Ω(singleton.GetKubeClient().Create(ctx, &v1.Secret{
Ω(singleton.KubeClient.Get().Create(ctx, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "ocm-cluster",
Namespace: StorageNamespace,
@ -103,13 +103,13 @@ var _ = Describe("Test Cluster API", func() {
Ω(err).To(Satisfy(IsInvalidClusterSecretError))
By("Create OCM ManagedCluster")
Ω(singleton.GetKubeClient().Create(ctx, &ocmclusterv1.ManagedCluster{
Ω(singleton.KubeClient.Get().Create(ctx, &ocmclusterv1.ManagedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "ocm-bad-cluster",
Namespace: StorageNamespace,
},
})).To(Succeed())
Ω(singleton.GetKubeClient().Create(ctx, &ocmclusterv1.ManagedCluster{
Ω(singleton.KubeClient.Get().Create(ctx, &ocmclusterv1.ManagedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "ocm-cluster",
Namespace: StorageNamespace,
@ -119,7 +119,7 @@ var _ = Describe("Test Cluster API", func() {
ManagedClusterClientConfigs: []ocmclusterv1.ClientConfig{{URL: "test-url"}},
},
})).To(Succeed())
Ω(singleton.GetKubeClient().Create(ctx, &ocmclusterv1.ManagedCluster{
Ω(singleton.KubeClient.Get().Create(ctx, &ocmclusterv1.ManagedCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: StorageNamespace,
@ -169,6 +169,27 @@ var _ = Describe("Test Cluster API", func() {
Expect(clusters.Items[0].Name).To(Equal("ocm-cluster"))
Expect(clusters.Items[1].Name).To(Equal("test-cluster"))
By("Test list clusters that are not control plane")
objs, err = c.List(ctx, &metainternalversion.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{
LabelClusterControlPlane: "false",
})})
Ω(err).To(Succeed())
clusters, ok = objs.(*ClusterList)
Ω(ok).To(BeTrue())
Expect(len(clusters.Items)).To(Equal(2))
Expect(clusters.Items[0].Name).To(Equal("ocm-cluster"))
Expect(clusters.Items[1].Name).To(Equal("test-cluster"))
By("Test list clusters that is control plane")
objs, err = c.List(ctx, &metainternalversion.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{
LabelClusterControlPlane: "true",
})})
Ω(err).To(Succeed())
clusters, ok = objs.(*ClusterList)
Ω(ok).To(BeTrue())
Expect(len(clusters.Items)).To(Equal(1))
Expect(clusters.Items[0].Name).To(Equal("local"))
By("Test print table")
_, err = c.ConvertToTable(ctx, cluster, nil)
Ω(err).To(Succeed())

View File

@ -0,0 +1,197 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource
import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/pkg/strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/kubevela/pkg/util/singleton"
)
type Typer interface {
GroupVersion() schema.GroupVersion
GroupVersionKind() schema.GroupVersionKind
GroupVersionKindList() schema.GroupVersionKind
GroupVersionResource() schema.GroupVersionResource
Kind() string
KindList() string
Resource() string
Namespaced() bool
}
type Codec interface {
Source() Typer
Target() Typer
Encode(src *unstructured.Unstructured) (*unstructured.Unstructured, error)
Decode(src *unstructured.Unstructured) (*unstructured.Unstructured, error)
}
type typer struct {
groupVersion schema.GroupVersion
kind string
resource string
kindList string
namespaced bool
}
var _ Typer = &typer{}
func (in *typer) GroupVersion() schema.GroupVersion {
return in.groupVersion
}
func (in *typer) GroupVersionKind() schema.GroupVersionKind {
return in.groupVersion.WithKind(in.kind)
}
func (in *typer) GroupVersionKindList() schema.GroupVersionKind {
return in.groupVersion.WithKind(in.kindList)
}
func (in *typer) GroupVersionResource() schema.GroupVersionResource {
return in.groupVersion.WithResource(in.resource)
}
func (in *typer) Kind() string {
return in.kind
}
func (in *typer) KindList() string {
return in.kindList
}
func (in *typer) Resource() string {
return in.resource
}
func (in *typer) Namespaced() bool {
return in.namespaced
}
func NewDefaultTyper(apiVersion string, kind string) (Typer, error) {
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
return nil, err
}
resource := strings.ToLower(kind) + "s"
namespaced := true
mappings, err := singleton.RESTMapper.Get().RESTMappings(gv.WithKind(kind).GroupKind(), gv.Version)
if err == nil && len(mappings) > 0 {
resource = mappings[0].Resource.Resource
namespaced = mappings[0].Scope.Name() == meta.RESTScopeNameNamespace
}
return &typer{
groupVersion: gv,
kind: kind,
resource: resource,
kindList: kind + "List",
namespaced: namespaced,
}, nil
}
const (
templateParameterKey = "parameter"
templateOutputKey = "output"
)
type templateCodec struct {
source, target Typer
encodeTemplate string
decodeTemplate string
}
var _ Codec = &templateCodec{}
func (in *templateCodec) Source() Typer {
return in.source
}
func (in *templateCodec) Target() Typer {
return in.target
}
func (in *templateCodec) Encode(source *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return in.convert(source, in.encodeTemplate, in.target)
}
func (in *templateCodec) Decode(target *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return in.convert(target, in.decodeTemplate, in.source)
}
func (in *templateCodec) convert(src *unstructured.Unstructured, template string, dest Typer) (*unstructured.Unstructured, error) {
bs, err := src.MarshalJSON()
if err != nil {
return nil, err
}
if template != "" {
ctx := cuecontext.New()
param := ctx.CompileBytes(bs)
val := ctx.CompileString(template).
FillPath(cue.ParsePath(templateParameterKey), param).
LookupPath(cue.ParsePath(templateOutputKey))
if err = val.Err(); err != nil {
return nil, err
}
if bs, err = val.MarshalJSON(); err != nil {
return nil, err
}
}
out := &unstructured.Unstructured{}
if err = out.UnmarshalJSON(bs); err != nil {
return nil, err
}
out.SetGroupVersionKind(dest.GroupVersionKind())
return out, nil
}
func (in *templateCodec) loadTyper(template string, path string) (Typer, error) {
templateVal := cuecontext.New().CompileString(template).Value()
if err := templateVal.Err(); err != nil {
return nil, err
}
apiVersion, err := templateVal.LookupPath(cue.ParsePath(path + ".apiVersion")).String()
if err != nil {
return nil, err
}
kind, err := templateVal.LookupPath(cue.ParsePath(path + ".kind")).String()
if err != nil {
return nil, err
}
return NewDefaultTyper(apiVersion, kind)
}
func NewTemplateCodec(encodeTemplate, decodeTemplate string) (Codec, error) {
var err error
codec := &templateCodec{
encodeTemplate: encodeTemplate,
decodeTemplate: decodeTemplate,
}
codec.source, err = codec.loadTyper(encodeTemplate, templateParameterKey)
if err != nil {
return nil, err
}
codec.target, err = codec.loadTyper(decodeTemplate, templateParameterKey)
if err != nil {
return nil, err
}
return codec, nil
}

View File

@ -0,0 +1,82 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/kubevela/prism/pkg/apis/dynamicresource"
)
var _ = Describe("Test codec", func() {
It("Test typer", func() {
_, err := dynamicresource.NewDefaultTyper("a/b/c", "")
Ω(err).NotTo(Succeed())
typer, err := dynamicresource.NewDefaultTyper("test.oam.dev/v1beta1", "Tester")
Ω(err).To(Succeed())
gv := schema.GroupVersion{Group: "test.oam.dev", Version: "v1beta1"}
Ω(typer.GroupVersion()).To(Equal(gv))
Ω(typer.GroupVersionKind()).To(Equal(gv.WithKind("Tester")))
Ω(typer.GroupVersionKindList()).To(Equal(gv.WithKind("TesterList")))
Ω(typer.GroupVersionResource()).To(Equal(gv.WithResource("testers")))
Ω(typer.Kind()).To(Equal("Tester"))
Ω(typer.KindList()).To(Equal("TesterList"))
Ω(typer.Resource()).To(Equal("testers"))
})
It("Test template codec", func() {
By("Normal codec")
codec, err := dynamicresource.NewTemplateCodec(encoderTemplate, decoderTemplate)
Ω(err).To(Succeed())
sourceGVK := schema.GroupVersionKind{Group: "test.oam.dev", Version: "v1alpha2", Kind: "Tester"}
targetGVK := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}
Ω(codec.Source().GroupVersionKind()).To(Equal(sourceGVK))
Ω(codec.Target().GroupVersionKind()).To(Equal(targetGVK))
By("Normal codec good encode and decode")
src := &unstructured.Unstructured{}
src.SetGroupVersionKind(sourceGVK)
tgt, err := codec.Encode(src)
Ω(err).To(Succeed())
Ω(tgt.GroupVersionKind()).To(Equal(targetGVK))
tgt = &unstructured.Unstructured{}
tgt.SetGroupVersionKind(targetGVK)
src, err = codec.Decode(tgt)
Ω(err).To(Succeed())
Ω(src.GroupVersionKind()).To(Equal(sourceGVK))
By("Normal codec bad encode")
src = &unstructured.Unstructured{}
src.SetGroupVersionKind(schema.GroupVersionKind{Group: "bad", Version: "unknown", Kind: "v0"})
_, err = codec.Encode(src)
Ω(err).NotTo(Succeed())
By("Codec with bad encoder")
_, err = dynamicresource.NewTemplateCodec(`bad-key: bad-val`, "")
Ω(err).NotTo(Succeed())
_, err = dynamicresource.NewTemplateCodec(`parameter: apiVersion: 1`, "")
Ω(err).NotTo(Succeed())
_, err = dynamicresource.NewTemplateCodec(`parameter: apiVersion: "v1"`, "")
Ω(err).NotTo(Succeed())
_, err = dynamicresource.NewTemplateCodec(encoderTemplate, "bad-good")
Ω(err).NotTo(Succeed())
})
})

View File

@ -0,0 +1,404 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource
import (
"context"
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/client-go/dynamic"
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
"github.com/kubevela/pkg/util/singleton"
)
type DynamicResource struct {
Uns *unstructured.Unstructured
codec Codec
}
func (in *DynamicResource) GetNamespace() string {
return in.Uns.GetNamespace()
}
func (in *DynamicResource) SetNamespace(namespace string) {
in.Uns.SetNamespace(namespace)
}
func (in *DynamicResource) GetName() string {
return in.Uns.GetName()
}
func (in *DynamicResource) SetName(name string) {
in.Uns.SetName(name)
}
func (in *DynamicResource) GetGenerateName() string {
return in.Uns.GetGenerateName()
}
func (in *DynamicResource) SetGenerateName(name string) {
in.Uns.SetGenerateName(name)
}
func (in *DynamicResource) GetUID() types.UID {
return in.Uns.GetUID()
}
func (in *DynamicResource) SetUID(uid types.UID) {
in.Uns.SetUID(uid)
}
func (in *DynamicResource) GetResourceVersion() string {
return in.Uns.GetResourceVersion()
}
func (in *DynamicResource) SetResourceVersion(version string) {
in.Uns.SetResourceVersion(version)
}
func (in *DynamicResource) GetGeneration() int64 {
return in.Uns.GetGeneration()
}
func (in *DynamicResource) SetGeneration(generation int64) {
in.Uns.SetGeneration(generation)
}
func (in *DynamicResource) GetSelfLink() string {
return in.Uns.GetSelfLink()
}
func (in *DynamicResource) SetSelfLink(selfLink string) {
in.Uns.SetSelfLink(selfLink)
}
func (in *DynamicResource) GetCreationTimestamp() metav1.Time {
return in.Uns.GetCreationTimestamp()
}
func (in *DynamicResource) SetCreationTimestamp(timestamp metav1.Time) {
in.Uns.SetCreationTimestamp(timestamp)
}
func (in *DynamicResource) GetDeletionTimestamp() *metav1.Time {
return in.Uns.GetDeletionTimestamp()
}
func (in *DynamicResource) SetDeletionTimestamp(timestamp *metav1.Time) {
in.Uns.SetDeletionTimestamp(timestamp)
}
func (in *DynamicResource) GetDeletionGracePeriodSeconds() *int64 {
return in.Uns.GetDeletionGracePeriodSeconds()
}
func (in *DynamicResource) SetDeletionGracePeriodSeconds(i *int64) {
in.Uns.SetDeletionGracePeriodSeconds(i)
}
func (in *DynamicResource) GetLabels() map[string]string {
return in.Uns.GetLabels()
}
func (in *DynamicResource) SetLabels(labels map[string]string) {
in.Uns.SetLabels(labels)
}
func (in *DynamicResource) GetAnnotations() map[string]string {
return in.Uns.GetAnnotations()
}
func (in *DynamicResource) SetAnnotations(annotations map[string]string) {
in.Uns.SetAnnotations(annotations)
}
func (in *DynamicResource) GetFinalizers() []string {
return in.Uns.GetFinalizers()
}
func (in *DynamicResource) SetFinalizers(finalizers []string) {
in.Uns.SetFinalizers(finalizers)
}
func (in *DynamicResource) GetOwnerReferences() []metav1.OwnerReference {
return in.Uns.GetOwnerReferences()
}
func (in *DynamicResource) SetOwnerReferences(references []metav1.OwnerReference) {
in.Uns.SetOwnerReferences(references)
}
func (in *DynamicResource) GetManagedFields() []metav1.ManagedFieldsEntry {
return in.Uns.GetManagedFields()
}
func (in *DynamicResource) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) {
in.Uns.SetManagedFields(managedFields)
}
var _ runtime.Object = &DynamicResource{}
var _ resource.Object = &DynamicResource{}
var _ rest.Storage = &DynamicResource{}
var _ rest.Getter = &DynamicResource{}
var _ rest.CreaterUpdater = &DynamicResource{}
var _ rest.Patcher = &DynamicResource{}
var _ rest.GracefulDeleter = &DynamicResource{}
var _ rest.Lister = &DynamicResource{}
var _ rest.GroupVersionKindProvider = &DynamicResource{}
var _ metav1.Object = &DynamicResource{}
type dynamicResourceObjectKind struct {
Typer
}
func (in *dynamicResourceObjectKind) SetGroupVersionKind(kind schema.GroupVersionKind) {}
func (in *dynamicResourceObjectKind) GroupVersionKind() schema.GroupVersionKind {
if in.Typer != nil {
return in.Typer.GroupVersionKind()
}
return schema.GroupVersionKind{}
}
var _ schema.ObjectKind = &dynamicResourceObjectKind{}
func (in *DynamicResource) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
return containingGV.WithKind(in.codec.Source().Kind())
}
func (in *DynamicResource) GroupVersionResource() schema.GroupVersionResource {
return in.codec.Source().GroupVersionResource()
}
func (in *DynamicResource) GetObjectKind() schema.ObjectKind {
objKind := &dynamicResourceObjectKind{nil}
if in.codec != nil {
objKind.Typer = in.codec.Source()
}
return objKind
}
func (in *DynamicResource) GetObjectMeta() *metav1.ObjectMeta {
return &metav1.ObjectMeta{
Name: in.Uns.GetName(),
GenerateName: in.Uns.GetGenerateName(),
Namespace: in.Uns.GetNamespace(),
UID: in.Uns.GetUID(),
ResourceVersion: in.Uns.GetResourceVersion(),
Generation: in.Uns.GetGeneration(),
CreationTimestamp: in.Uns.GetCreationTimestamp(),
DeletionTimestamp: in.Uns.GetDeletionTimestamp(),
DeletionGracePeriodSeconds: in.Uns.GetDeletionGracePeriodSeconds(),
Labels: in.Uns.GetLabels(),
Annotations: in.Uns.GetAnnotations(),
OwnerReferences: in.Uns.GetOwnerReferences(),
Finalizers: in.Uns.GetFinalizers(),
ManagedFields: in.Uns.GetManagedFields(),
}
}
func (in *DynamicResource) NamespaceScoped() bool {
return in.codec.Source().Namespaced()
}
func (in *DynamicResource) New() runtime.Object {
dr := &DynamicResource{
Uns: &unstructured.Unstructured{},
codec: in.codec,
}
if dr.codec != nil {
dr.Uns.SetGroupVersionKind(dr.codec.Source().GroupVersionKind())
}
return dr
}
func (in *DynamicResource) NewList() runtime.Object {
drs := &DynamicResourceList{
UnsList: &unstructured.UnstructuredList{},
codec: in.codec,
}
if drs.codec != nil {
drs.UnsList.SetGroupVersionKind(drs.codec.Source().GroupVersionKindList())
}
return drs
}
func (in *DynamicResource) Destroy() {}
func (in *DynamicResource) GetGroupVersionResource() schema.GroupVersionResource {
return in.codec.Source().GroupVersionResource()
}
func (in *DynamicResource) GetGroupVersionKind() schema.GroupVersionKind {
return in.codec.Source().GroupVersionKind()
}
func (in *DynamicResource) GetGroupVersion() schema.GroupVersion {
return in.codec.Source().GroupVersion()
}
func (in *DynamicResource) IsStorageVersion() bool {
return true
}
func (in *DynamicResource) DeepCopyObject() runtime.Object {
return &DynamicResource{
Uns: in.Uns.DeepCopy(),
codec: in.codec,
}
}
func (in *DynamicResource) resourceInterface(ctx context.Context) dynamic.ResourceInterface {
var ri dynamic.ResourceInterface = singleton.DynamicClient.Get().Resource(in.codec.Target().GroupVersionResource())
if in.codec.Target().Namespaced() {
ri = ri.(dynamic.NamespaceableResourceInterface).Namespace(request.NamespaceValue(ctx))
}
return ri
}
func (in *DynamicResource) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
obj, err := in.resourceInterface(ctx).Get(ctx, name, *options)
if err != nil {
return nil, err
}
decoded, err := in.codec.Decode(obj)
return decoded, err
}
func (in *DynamicResource) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
o := obj.(*DynamicResource)
encoded, err := in.codec.Encode(o.Uns)
if err != nil {
return nil, err
}
created, err := in.resourceInterface(ctx).Create(ctx, encoded, *options)
if err != nil {
return nil, err
}
decoded, err := in.codec.Decode(created)
return decoded, err
}
func (in *DynamicResource) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
decoded, err := in.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, false, err
}
updated, err := objInfo.UpdatedObject(ctx, decoded)
if err != nil {
return nil, false, err
}
encoded, err := in.codec.Encode(updated.(*DynamicResource).Uns)
if err != nil {
return nil, false, err
}
encoded, err = in.resourceInterface(ctx).Update(ctx, encoded, *options)
if err != nil {
return nil, false, err
}
decoded, err = in.codec.Decode(encoded)
return decoded, false, err
}
func (in *DynamicResource) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
obj, err := in.resourceInterface(ctx).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, false, err
}
if err = in.resourceInterface(ctx).Delete(ctx, name, *options); err != nil {
return nil, false, err
}
decoded, err := in.codec.Decode(obj)
return decoded, false, err
}
func (in *DynamicResource) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
v1Options := &metav1.ListOptions{}
if err := internalversion.Convert_internalversion_ListOptions_To_v1_ListOptions(options, v1Options, nil); err != nil {
return nil, err
}
objs, err := in.resourceInterface(ctx).List(ctx, *v1Options)
if err != nil {
return nil, err
}
decoded := &unstructured.UnstructuredList{}
decoded.SetGroupVersionKind(in.codec.Source().GroupVersionKindList())
for i := range objs.Items {
d, err := in.codec.Decode(&objs.Items[i])
if err != nil {
return nil, err
}
decoded.Items = append(decoded.Items, *d)
}
return decoded, nil
}
func (in *DynamicResource) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
// TODO
t := &metav1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{{
Name: "Name",
Type: "string",
}, {
Name: "CreateAt",
Type: "dateTime",
}},
Rows: []metav1.TableRow{},
}
var dat []*unstructured.Unstructured
switch obj := object.(type) {
case *unstructured.Unstructured:
dat = append(dat, obj)
case *unstructured.UnstructuredList:
for i := range obj.Items {
dat = append(dat, &obj.Items[i])
}
default:
return nil, fmt.Errorf("unknown type %T", object)
}
for _, u := range dat {
row := metav1.TableRow{
Object: runtime.RawExtension{Object: u},
}
row.Cells = []interface{}{u.GetName(), u.GetCreationTimestamp()}
t.Rows = append(t.Rows, row)
}
return t, nil
}
func (in *DynamicResource) MarshalJSON() ([]byte, error) {
return json.Marshal(in.Uns)
}
func (in *DynamicResource) UnmarshalJSON(bs []byte) error {
in.Uns = &unstructured.Unstructured{}
return in.Uns.UnmarshalJSON(bs)
}

View File

@ -0,0 +1,58 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource
import (
"encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type DynamicResourceList struct {
UnsList *unstructured.UnstructuredList
codec Codec
}
var _ runtime.Object = &DynamicResourceList{}
func (in *DynamicResourceList) GetObjectKind() schema.ObjectKind {
return in
}
func (in *DynamicResourceList) DeepCopyObject() runtime.Object {
return &DynamicResourceList{
UnsList: in.UnsList.DeepCopy(),
codec: in.codec,
}
}
func (in *DynamicResourceList) SetGroupVersionKind(kind schema.GroupVersionKind) {}
func (in *DynamicResourceList) GroupVersionKind() schema.GroupVersionKind {
return in.codec.Source().GroupVersionKindList()
}
func (in *DynamicResourceList) MarshalJSON() ([]byte, error) {
return json.Marshal(in.UnsList)
}
func (in *DynamicResourceList) UnmarshalJSON(bs []byte) error {
in.UnsList = &unstructured.UnstructuredList{}
return in.UnsList.UnmarshalJSON(bs)
}

View File

@ -14,18 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package log
package dynamicresource
import (
"flag"
"github.com/spf13/cobra"
"k8s.io/klog/v2"
)
// AddLogFlags add log flags to command
func AddLogFlags(cmd *cobra.Command) {
fs := flag.NewFlagSet("", 0)
klog.InitFlags(fs)
cmd.Flags().AddGoFlagSet(fs)
func NewDynamicResourceWithCodec(encoderTemplate, decoderTemplate string) (obj *DynamicResource, err error) {
obj = &DynamicResource{}
obj.codec, err = NewTemplateCodec(encoderTemplate, decoderTemplate)
return obj, err
}

View File

@ -0,0 +1,31 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
_ "github.com/kubevela/prism/test/bootstrap"
)
func TestDynamicResource(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Test Dynamic Resource")
}

View File

@ -0,0 +1,172 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dynamicresource_test
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/pointer"
"github.com/kubevela/prism/pkg/apis/dynamicresource"
)
const (
encoderTemplate = `
parameter: {
apiVersion: "test.oam.dev/v1alpha2"
kind: "Tester"
metadata: {...}
}
output: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: parameter.metadata
data: {}
}
`
decoderTemplate = `
parameter: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: {...}
data: {...}
}
output: {
apiVersion: "test.oam.dev/v1alpha2"
kind: "Tester"
metadata: parameter.metadata
}
`
)
func newDynamicResource() (*dynamicresource.DynamicResource, error) {
return dynamicresource.NewDynamicResourceWithCodec(encoderTemplate, decoderTemplate)
}
func testSetterAndGetter[T any](setter func(T), getter func() T, val T) {
setter(val)
Ω(getter()).Should(Equal(val))
}
var _ = Describe("Test dynamic resource", func() {
It("Test types", func() {
store, err := newDynamicResource()
Ω(err).To(Succeed())
dr := store.New().(*dynamicresource.DynamicResource)
testSetterAndGetter(dr.SetNamespace, dr.GetNamespace, "ns")
testSetterAndGetter(dr.SetName, dr.GetName, "name")
testSetterAndGetter(dr.SetUID, dr.GetUID, "name")
testSetterAndGetter(dr.SetGenerateName, dr.GetGenerateName, "gn")
testSetterAndGetter(dr.SetResourceVersion, dr.GetResourceVersion, "rv")
testSetterAndGetter(dr.SetGeneration, dr.GetGeneration, 1)
testSetterAndGetter(dr.SetSelfLink, dr.GetSelfLink, "sl")
t := time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local)
testSetterAndGetter(dr.SetCreationTimestamp, dr.GetCreationTimestamp, metav1.Time{Time: t})
testSetterAndGetter(dr.SetDeletionTimestamp, dr.GetDeletionTimestamp, &metav1.Time{Time: t})
testSetterAndGetter(dr.SetDeletionGracePeriodSeconds, dr.GetDeletionGracePeriodSeconds, pointer.Int64(1))
testSetterAndGetter(dr.SetLabels, dr.GetLabels, map[string]string{"x": "y"})
testSetterAndGetter(dr.SetAnnotations, dr.GetAnnotations, map[string]string{"x": "x"})
testSetterAndGetter(dr.SetFinalizers, dr.GetFinalizers, []string{"f"})
testSetterAndGetter(dr.SetOwnerReferences, dr.GetOwnerReferences, []metav1.OwnerReference{{Kind: "k"}})
testSetterAndGetter(dr.SetManagedFields, dr.GetManagedFields, []metav1.ManagedFieldsEntry{{Manager: "m"}})
Ω(dr.GroupVersionKind(schema.GroupVersion{})).To(Equal(schema.GroupVersionKind{Kind: "Tester"}))
gv := schema.GroupVersion{Group: "test.oam.dev", Version: "v1alpha2"}
gvr := gv.WithResource("testers")
gvk := gv.WithKind("Tester")
Ω(dr.GroupVersionResource()).To(Equal(gvr))
objKind := dr.GetObjectKind()
objKind.SetGroupVersionKind(schema.GroupVersionKind{})
Ω(objKind.GroupVersionKind()).To(Equal(gvk))
Ω(dr.NamespaceScoped()).To(BeTrue())
defer dr.Destroy()
Ω(dr.GetGroupVersionResource()).To(Equal(gvr))
Ω(dr.GetGroupVersionKind()).To(Equal(gvk))
Ω(dr.GetGroupVersion()).To(Equal(gv))
Ω(dr.IsStorageVersion()).To(BeTrue())
Ω(dr.DeepCopyObject().(*dynamicresource.DynamicResource).GetObjectMeta()).To(Equal(dr.GetObjectMeta()))
bs, err := dr.MarshalJSON()
Ω(err).To(Succeed())
ndr := dr.New().(*dynamicresource.DynamicResource)
Ω(ndr.UnmarshalJSON(bs)).To(Succeed())
Ω(ndr.GetObjectMeta()).To(Equal(dr.GetObjectMeta()))
drs := dr.NewList().(*dynamicresource.DynamicResourceList)
drs.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
Ω(drs.GetObjectKind().GroupVersionKind()).To(Equal(gv.WithKind("TesterList")))
drs.UnsList.Items = make([]unstructured.Unstructured, 1)
Ω(len(drs.DeepCopyObject().(*dynamicresource.DynamicResourceList).UnsList.Items)).To(Equal(1))
ndrs := dr.NewList().(*dynamicresource.DynamicResourceList)
bs, err = drs.MarshalJSON()
Ω(err).To(Succeed())
Ω(ndrs.UnmarshalJSON(bs)).To(Succeed())
Ω(len(ndrs.DeepCopyObject().(*dynamicresource.DynamicResourceList).UnsList.Items)).To(Equal(1))
})
It("Test CURD API", func() {
store, err := newDynamicResource()
Ω(err).To(Succeed())
ctx := request.WithNamespace(context.Background(), "default")
By("Create")
dr := store.New().(*dynamicresource.DynamicResource)
dr.SetName("example")
dr.SetLabels(map[string]string{"key": "val"})
_, err = store.Create(ctx, dr, nil, &metav1.CreateOptions{})
Ω(err).To(Succeed())
By("Get")
obj, err := store.Get(ctx, "example", &metav1.GetOptions{})
Ω(err).To(Succeed())
uns := obj.(*unstructured.Unstructured)
Ω(uns.GetLabels()["key"]).To(Equal("val"))
_, err = store.ConvertToTable(ctx, uns, nil)
Ω(err).To(Succeed())
By("Update")
dr.SetLabels(map[string]string{"key": "value"})
_, _, err = store.Update(ctx, dr.GetName(), rest.DefaultUpdatedObjectInfo(dr), nil, nil, false, &metav1.UpdateOptions{})
Ω(err).To(Succeed())
By("List")
objs, err := store.List(ctx, &internalversion.ListOptions{})
Ω(err).To(Succeed())
unsList := objs.(*unstructured.UnstructuredList)
Ω(len(unsList.Items)).To(Equal(1))
Ω(unsList.Items[0].GetLabels()["key"]).To(Equal("value"))
_, err = store.ConvertToTable(ctx, unsList, nil)
Ω(err).To(Succeed())
By("Delete")
_, _, err = store.Delete(ctx, "example", nil, &metav1.DeleteOptions{})
Ω(err).To(Succeed())
_, err = store.Get(ctx, "example", &metav1.GetOptions{})
Ω(kerrors.IsNotFound(err)).To(BeTrue())
})
})

View File

@ -23,8 +23,9 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/apis/o11y/config"
"github.com/kubevela/prism/pkg/util/apiserver"
)
// GrafanaClient client for operate grafana

View File

@ -31,6 +31,7 @@ func (in *Grafana) ToSecret() *corev1.Secret {
secret.ObjectMeta = in.ObjectMeta
secret.SetName(grafanaSecretNamePrefix + in.GetName())
secret.SetNamespace(config.ObservabilityNamespace)
secret.SetOwnerReferences(nil)
annotations := in.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}

View File

@ -66,7 +66,7 @@ var (
{Name: "Name", Type: "string", Format: "name", Description: "the name of the Grafana"},
{Name: "Endpoint", Type: "string", Description: "the endpoint"},
{Name: "Credential_Type", Type: "string", Description: "the credential type"},
{Name: "Labels", Type: "string", Description: "the labels of the Grafana"},
{Name: "Labels", Type: "string", Description: "the labels of the Grafana", Priority: 10},
{Name: "Creation_Timestamp", Type: "dateTime", Description: "the creation timestamp of the Grafana", Priority: 10},
}
)

View File

@ -26,8 +26,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/pkg/util/singleton"
)
// Grafana defines the instance of grafana
@ -88,6 +88,9 @@ func (in *Grafana) New() runtime.Object {
return &Grafana{}
}
// Destroy .
func (in *Grafana) Destroy() {}
// NewList return a new list instance of the resource
func (in *Grafana) NewList() runtime.Object {
return &GrafanaList{}
@ -118,22 +121,22 @@ const (
// Get finds a resource in the storage by name and returns it.
func (in *Grafana) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return NewGrafanaClient(singleton.GetKubeClient()).Get(ctx, name)
return NewGrafanaClient(singleton.KubeClient.Get()).Get(ctx, name)
}
// List selects resources in the storage which match to the selector. 'options' can be nil.
func (in *Grafana) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
return NewGrafanaClient(singleton.GetKubeClient()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
return NewGrafanaClient(singleton.KubeClient.Get()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
}
// Create creates a new version of a resource.
func (in *Grafana) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return obj, NewGrafanaClient(singleton.GetKubeClient()).Create(ctx, obj.(*Grafana))
return obj, NewGrafanaClient(singleton.KubeClient.Get()).Create(ctx, obj.(*Grafana))
}
// Update finds a resource in the storage and updates it.
func (in *Grafana) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaClient(singleton.GetKubeClient())
cli := NewGrafanaClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}
@ -145,7 +148,7 @@ func (in *Grafana) Update(ctx context.Context, name string, objInfo rest.Updated
// Delete finds a resource in the storage and deletes it.
func (in *Grafana) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaClient(singleton.GetKubeClient())
cli := NewGrafanaClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}

View File

@ -19,6 +19,7 @@ package v1alpha1
import (
"context"
"github.com/kubevela/pkg/util/k8s"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
@ -30,19 +31,19 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/utils/pointer"
"github.com/kubevela/pkg/util/singleton"
"github.com/kubevela/prism/pkg/apis/o11y/config"
"github.com/kubevela/prism/pkg/util/singleton"
testutil "github.com/kubevela/prism/test/util"
)
var _ = Describe("Test Grafana API", func() {
BeforeEach(func() {
Ω(testutil.CreateNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.EnsureNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
})
AfterEach(func() {
Ω(testutil.DeleteNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.ClearNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
})
It("Test Grafana API", func() {
@ -89,7 +90,7 @@ var _ = Describe("Test Grafana API", func() {
}
for _, secret := range []*corev1.Secret{secret1, secret2, secret3} {
secret.SetNamespace(config.ObservabilityNamespace)
Ω(singleton.GetKubeClient().Create(context.Background(), secret)).To(Succeed())
Ω(singleton.KubeClient.Get().Create(context.Background(), secret)).To(Succeed())
}
By("Test Get Grafana")

View File

@ -25,8 +25,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/pkg/util/apiserver"
grafanav1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafana/v1alpha1"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/subresource"
)

View File

@ -38,6 +38,7 @@ func (in *GrafanaDashboard) ToRequestBody() ([]byte, error) {
return nil, err
}
dashboard["uid"] = subresource.NewCompoundName(in.GetName()).SubResourceName
delete(dashboard, "id")
data := map[string]interface{}{"dashboard": dashboard}
if labels := in.GetLabels(); labels != nil {
if raw := labels[grafanaDashboardFolderIdLabelKey]; raw != "" {

View File

@ -23,7 +23,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/subresource"
)

View File

@ -26,6 +26,8 @@ import (
"strings"
"testing"
"github.com/kubevela/pkg/util/k8s"
"github.com/kubevela/pkg/util/singleton"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -37,7 +39,6 @@ import (
grafanav1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafana/v1alpha1"
"github.com/kubevela/prism/pkg/util/subresource"
_ "github.com/kubevela/prism/test/bootstrap"
testutil "github.com/kubevela/prism/test/util"
)
func TestGrafanaDashboard(t *testing.T) {
@ -51,7 +52,7 @@ var _ = Describe("Test GrafanaDashboard API", func() {
var data map[string][]byte
BeforeEach(func() {
Ω(testutil.CreateNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.EnsureNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
data = map[string][]byte{}
mockServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
p := request.Method + " " + request.URL.Path
@ -96,7 +97,7 @@ var _ = Describe("Test GrafanaDashboard API", func() {
})
AfterEach(func() {
Ω(testutil.DeleteNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.ClearNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
mockServer.Close()
})

View File

@ -26,8 +26,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/pkg/util/singleton"
)
// GrafanaDashboard is a reflection api for Grafana Datasource
@ -70,6 +70,9 @@ func (in *GrafanaDashboard) New() runtime.Object {
return &GrafanaDashboard{}
}
// Destroy .
func (in *GrafanaDashboard) Destroy() {}
// NewList return a new list instance of the resource
func (in *GrafanaDashboard) NewList() runtime.Object {
return &GrafanaDashboardList{}
@ -92,15 +95,15 @@ func (in *GrafanaDashboard) ShortNames() []string {
// Get finds a resource in the storage by name and returns it.
func (in *GrafanaDashboard) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return NewGrafanaDashboardClient(singleton.GetKubeClient()).Get(ctx, name)
return NewGrafanaDashboardClient(singleton.KubeClient.Get()).Get(ctx, name)
}
func (in *GrafanaDashboard) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return obj, NewGrafanaDashboardClient(singleton.GetKubeClient()).Create(ctx, obj.(*GrafanaDashboard))
return obj, NewGrafanaDashboardClient(singleton.KubeClient.Get()).Create(ctx, obj.(*GrafanaDashboard))
}
func (in *GrafanaDashboard) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaDashboardClient(singleton.GetKubeClient())
cli := NewGrafanaDashboardClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}
@ -111,7 +114,7 @@ func (in *GrafanaDashboard) Update(ctx context.Context, name string, objInfo res
}
func (in *GrafanaDashboard) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaDashboardClient(singleton.GetKubeClient())
cli := NewGrafanaDashboardClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}
@ -120,7 +123,7 @@ func (in *GrafanaDashboard) Delete(ctx context.Context, name string, deleteValid
func (in *GrafanaDashboard) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
if name := apiserver.GetMetadataNameInFieldSelectorFromInternalVersionListOptions(options); name != nil {
return NewGrafanaDashboardClient(singleton.GetKubeClient()).Get(ctx, *name)
return NewGrafanaDashboardClient(singleton.KubeClient.Get()).Get(ctx, *name)
}
return NewGrafanaDashboardClient(singleton.GetKubeClient()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
return NewGrafanaDashboardClient(singleton.KubeClient.Get()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
}

View File

@ -26,8 +26,9 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubevela/pkg/util/apiserver"
grafanav1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafana/v1alpha1"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/subresource"
)

View File

@ -23,7 +23,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/subresource"
)

View File

@ -26,6 +26,8 @@ import (
"strings"
"testing"
"github.com/kubevela/pkg/util/k8s"
"github.com/kubevela/pkg/util/singleton"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -33,12 +35,12 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/utils/pointer"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/apis/o11y/config"
grafanav1alpha1 "github.com/kubevela/prism/pkg/apis/o11y/grafana/v1alpha1"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/subresource"
_ "github.com/kubevela/prism/test/bootstrap"
testutil "github.com/kubevela/prism/test/util"
)
func TestGrafanaDatasource(t *testing.T) {
@ -52,7 +54,7 @@ var _ = Describe("Test GrafanaDatasource API", func() {
var data map[string][]byte
BeforeEach(func() {
Ω(testutil.CreateNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.EnsureNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
data = map[string][]byte{}
mockServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
p := request.Method + " " + request.URL.Path
@ -107,7 +109,7 @@ var _ = Describe("Test GrafanaDatasource API", func() {
})
AfterEach(func() {
Ω(testutil.DeleteNamespace(config.ObservabilityNamespace)).To(Succeed())
Ω(k8s.ClearNamespace(context.Background(), singleton.KubeClient.Get(), config.ObservabilityNamespace)).To(Succeed())
mockServer.Close()
})

View File

@ -26,8 +26,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/apiserver-runtime/pkg/builder/resource"
"github.com/kubevela/prism/pkg/util/apiserver"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/apiserver"
"github.com/kubevela/pkg/util/singleton"
)
// GrafanaDatasource is a reflection api for Grafana Datasource
@ -70,6 +70,9 @@ func (in *GrafanaDatasource) New() runtime.Object {
return &GrafanaDatasource{}
}
// Destroy .
func (in *GrafanaDatasource) Destroy() {}
// NewList return a new list instance of the resource
func (in *GrafanaDatasource) NewList() runtime.Object {
return &GrafanaDatasourceList{}
@ -92,15 +95,15 @@ func (in *GrafanaDatasource) ShortNames() []string {
// Get finds a resource in the storage by name and returns it.
func (in *GrafanaDatasource) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return NewGrafanaDatasourceClient(singleton.GetKubeClient()).Get(ctx, name)
return NewGrafanaDatasourceClient(singleton.KubeClient.Get()).Get(ctx, name)
}
func (in *GrafanaDatasource) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return obj, NewGrafanaDatasourceClient(singleton.GetKubeClient()).Create(ctx, obj.(*GrafanaDatasource))
return obj, NewGrafanaDatasourceClient(singleton.KubeClient.Get()).Create(ctx, obj.(*GrafanaDatasource))
}
func (in *GrafanaDatasource) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaDatasourceClient(singleton.GetKubeClient())
cli := NewGrafanaDatasourceClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}
@ -111,7 +114,7 @@ func (in *GrafanaDatasource) Update(ctx context.Context, name string, objInfo re
}
func (in *GrafanaDatasource) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (obj runtime.Object, _ bool, err error) {
cli := NewGrafanaDatasourceClient(singleton.GetKubeClient())
cli := NewGrafanaDatasourceClient(singleton.KubeClient.Get())
if obj, err = cli.Get(ctx, name); err != nil {
return nil, false, err
}
@ -120,7 +123,7 @@ func (in *GrafanaDatasource) Delete(ctx context.Context, name string, deleteVali
func (in *GrafanaDatasource) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
if name := apiserver.GetMetadataNameInFieldSelectorFromInternalVersionListOptions(options); name != nil {
return NewGrafanaDatasourceClient(singleton.GetKubeClient()).Get(ctx, *name)
return NewGrafanaDatasourceClient(singleton.KubeClient.Get()).Get(ctx, *name)
}
return NewGrafanaDatasourceClient(singleton.GetKubeClient()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
return NewGrafanaDatasourceClient(singleton.KubeClient.Get()).List(ctx, apiserver.NewMatchingLabelSelectorFromInternalVersionListOptions(options))
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"k8s.io/apiserver/pkg/server"
"github.com/kubevela/pkg/util/singleton"
)
// StartDefaultDynamicAPIServer run default dynamic apiserver in backend
func StartDefaultDynamicAPIServer(ctx server.PostStartHookContext) error {
DefaultDynamicAPIServer = NewDynamicAPIServer(
singleton.GenericAPIServer.Get(),
singleton.APIServerConfig.Get())
go StartDynamicResourceFactoryWithConfigMapInformer(ctx.StopCh)
//go StartDynamicResourceFactoryWithConfigMapInformer(ctx.StopCh)
return nil
}

View File

@ -0,0 +1,245 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"path"
"sync"
"time"
"cuelang.org/go/pkg/strings"
"github.com/emicklei/go-restful/v3"
"golang.org/x/exp/slices"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
genericapi "k8s.io/apiserver/pkg/endpoints"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/server"
)
var DefaultDynamicAPIServer *DynamicAPIServer
type ResourceProvider interface {
rest.Storage
GetGroupVersion() schema.GroupVersion
GetGroupVersionKind() schema.GroupVersionKind
GetGroupVersionResource() schema.GroupVersionResource
}
type DynamicAPIServer struct {
server *server.GenericAPIServer
config *server.Config
scheme *runtime.Scheme
parameterCodec runtime.ParameterCodec
negotiatedSerializer runtime.NegotiatedSerializer
mu sync.Mutex
apiGroups map[string]*metav1.APIGroup
apiGroupVersions map[schema.GroupVersion]*genericapi.APIGroupVersion
apiGroupVersionHandlers map[schema.GroupVersion]*restful.WebService
}
func NewDynamicAPIServer(svr *server.GenericAPIServer, config *server.Config) *DynamicAPIServer {
s := &DynamicAPIServer{server: svr, config: config}
s.scheme = runtime.NewScheme()
s.parameterCodec = runtime.NewParameterCodec(s.scheme)
s.negotiatedSerializer = serializer.NewCodecFactory(s.scheme)
s.apiGroups = map[string]*metav1.APIGroup{}
s.apiGroupVersions = map[schema.GroupVersion]*genericapi.APIGroupVersion{}
metav1.AddToGroupVersion(s.scheme, metav1.Unversioned)
return s
}
func (in *DynamicAPIServer) removeWebService(prefix string) {
var toRemove *restful.WebService
for _, svc := range in.server.Handler.GoRestfulContainer.RegisteredWebServices() {
if svc.RootPath() == prefix {
toRemove = svc
break
}
}
if toRemove != nil {
_ = in.server.Handler.GoRestfulContainer.Remove(toRemove)
}
}
func NewGroupVersionForDiscovery(gv schema.GroupVersion) metav1.GroupVersionForDiscovery {
return metav1.GroupVersionForDiscovery{
GroupVersion: gv.String(),
Version: gv.Version,
}
}
func (in *DynamicAPIServer) AddGroupDiscovery(gv schema.GroupVersion) {
in.mu.Lock()
defer in.mu.Unlock()
gv4discovery := NewGroupVersionForDiscovery(gv)
apiGroup, exists := in.apiGroups[gv.Group]
if !exists {
apiGroup = &metav1.APIGroup{
Name: gv.Group,
Versions: []metav1.GroupVersionForDiscovery{},
PreferredVersion: gv4discovery,
}
}
if slices.Contains(apiGroup.Versions, gv4discovery) {
return
}
apiGroup.Versions = append(apiGroup.Versions, gv4discovery)
in.apiGroups[gv.Group] = apiGroup
in.server.DiscoveryGroupManager.RemoveGroup(gv.Group)
in.server.DiscoveryGroupManager.AddGroup(*apiGroup)
}
func (in *DynamicAPIServer) RemoveGroupDiscovery(gv schema.GroupVersion) {
in.mu.Lock()
defer in.mu.Unlock()
apiGroup, exists := in.apiGroups[gv.Group]
if !exists {
return
}
gv4discovery := NewGroupVersionForDiscovery(gv)
if idx := slices.Index(apiGroup.Versions, gv4discovery); idx >= 0 {
apiGroup.Versions = slices.Delete(apiGroup.Versions, idx, idx)
}
if len(apiGroup.Versions) > 0 && apiGroup.PreferredVersion == gv4discovery {
apiGroup.PreferredVersion = apiGroup.Versions[0]
}
if len(apiGroup.Versions) == 0 {
in.server.DiscoveryGroupManager.RemoveGroup(gv.Group)
delete(in.apiGroups, gv.Group)
return
}
in.apiGroups[gv.Group] = apiGroup
in.server.DiscoveryGroupManager.RemoveGroup(gv.Group)
in.server.DiscoveryGroupManager.AddGroup(*apiGroup)
}
func (in *DynamicAPIServer) AddGroupVersionResourceHandler(gvr schema.GroupVersionResource, storage rest.Storage) error {
in.mu.Lock()
defer in.mu.Unlock()
gv := gvr.GroupVersion()
apiGroupVersion, exists := in.apiGroupVersions[gv]
if !exists {
apiGroupVersion = &genericapi.APIGroupVersion{
Root: server.APIGroupPrefix,
Storage: map[string]rest.Storage{},
GroupVersion: gv,
MetaGroupVersion: nil,
ParameterCodec: in.parameterCodec,
Serializer: in.negotiatedSerializer,
Creater: in.scheme,
Convertor: in.scheme,
ConvertabilityChecker: in.scheme,
UnsafeConvertor: runtime.UnsafeObjectConvertor(in.scheme),
Defaulter: in.scheme,
Typer: in.scheme,
Namer: runtime.Namer(meta.NewAccessor()),
EquivalentResourceRegistry: in.server.EquivalentResourceRegistry,
Admit: in.config.AdmissionControl,
MinRequestTimeout: time.Duration(in.config.MinRequestTimeout) * time.Second,
MaxRequestBodyBytes: in.config.MaxRequestBodyBytes,
Authorizer: in.server.Authorizer,
}
}
apiGroupVersion.Storage[gvr.Resource] = storage
in.apiGroupVersions[gv] = apiGroupVersion
in.removeGroupVersionHandler(gv)
_, _, err := apiGroupVersion.InstallREST(in.server.Handler.GoRestfulContainer)
return err
}
func (in *DynamicAPIServer) RemoveGroupVersionResourceHandler(gvr schema.GroupVersionResource, storage rest.Storage) error {
in.mu.Lock()
defer in.mu.Unlock()
gv := gvr.GroupVersion()
apiGroupVersion, exists := in.apiGroupVersions[gv]
if !exists {
return nil
}
delete(apiGroupVersion.Storage, gvr.Resource)
in.removeGroupVersionHandler(gv)
if len(apiGroupVersion.Storage) == 0 {
delete(in.apiGroupVersions, gv)
return nil
}
_, _, err := apiGroupVersion.InstallREST(in.server.Handler.GoRestfulContainer)
return err
}
func (in *DynamicAPIServer) removeGroupVersionHandler(gv schema.GroupVersion) {
prefix := path.Join(server.APIGroupPrefix, gv.Group, gv.Version)
webservices := in.server.Handler.GoRestfulContainer.RegisteredWebServices()
if idx := slices.IndexFunc(webservices, func(ws *restful.WebService) bool {
return ws.RootPath() == prefix
}); idx >= 0 {
_ = in.server.Handler.GoRestfulContainer.Remove(webservices[idx])
}
}
func (in *DynamicAPIServer) AddResource(r ResourceProvider) error {
in.AddScheme(r.GetGroupVersionKind(), r)
in.AddGroupDiscovery(r.GetGroupVersion())
return in.AddGroupVersionResourceHandler(r.GetGroupVersionResource(), r)
}
func (in *DynamicAPIServer) RemoveResource(r ResourceProvider) error {
in.RemoveScheme(r.GetGroupVersionKind())
in.RemoveGroupDiscovery(r.GetGroupVersion())
return in.RemoveGroupVersionResourceHandler(r.GetGroupVersionResource(), r)
}
func (in *DynamicAPIServer) AddScheme(gvk schema.GroupVersionKind, storage rest.Storage) {
in.mu.Lock()
defer in.mu.Unlock()
in.scheme.AddKnownTypeWithName(gvk, storage.New())
metav1.AddToGroupVersion(in.scheme, gvk.GroupVersion())
if listStorage, ok := storage.(rest.Lister); ok {
in.scheme.AddKnownTypeWithName(gvk.GroupVersion().WithKind(gvk.Kind+"List"), listStorage.NewList())
}
}
func (in *DynamicAPIServer) RemoveScheme(gvk schema.GroupVersionKind) {
in.mu.Lock()
defer in.mu.Unlock()
newScheme := runtime.NewScheme()
for _gvk := range in.scheme.AllKnownTypes() {
_gvk.Kind = strings.TrimSuffix(_gvk.Kind, "List")
if _gvk == gvk {
continue
}
obj, err := in.scheme.New(_gvk)
if err != nil {
continue
}
newScheme.AddKnownTypeWithName(_gvk, obj)
if _gvk.Version != runtime.APIVersionInternal {
metav1.AddToGroupVersion(newScheme, _gvk.GroupVersion())
}
}
in.scheme = newScheme
in.parameterCodec = runtime.NewParameterCodec(in.scheme)
in.negotiatedSerializer = serializer.NewCodecFactory(in.scheme)
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"github.com/kubevela/pkg/util/singleton"
"github.com/kubevela/prism/pkg/apis/dynamicresource"
)
const (
defaultNamespace = "vela-system"
encoderKey = "encoder"
decoderKey = "decoder"
)
func StartDynamicResourceFactoryWithConfigMapInformer(stopCh <-chan struct{}) {
factory := informers.NewSharedInformerFactoryWithOptions(
singleton.StaticClient.Get(), 0,
informers.WithNamespace(defaultNamespace))
informer := factory.Core().V1().ConfigMaps().Informer()
defer runtime.HandleCrash()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if err := addResource(obj); err != nil {
klog.Error(err)
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
if err := removeResource(oldObj); err != nil {
klog.Error(err)
}
if err := addResource(newObj); err != nil {
klog.Error(err)
}
},
DeleteFunc: func(obj interface{}) {
if err := removeResource(obj); err != nil {
klog.Error(err)
}
},
})
informer.Run(stopCh)
}
func handleResource(obj interface{}, action string, handler func(ResourceProvider) error) error {
var r ResourceProvider
var err error
switch o := obj.(type) {
case *corev1.ConfigMap:
r, err = newDynamicResourceFromConfigMap(o)
default:
return fmt.Errorf("cannot recognize %T type", obj)
}
if err != nil {
return err
}
if r == nil {
return nil
}
klog.Infof("Handle Resource %s.%s (%s)", r.GetGroupVersionResource().Resource, r.GetGroupVersion(), action)
return handler(r)
}
func addResource(obj interface{}) error {
return handleResource(obj, "Add", DefaultDynamicAPIServer.AddResource)
}
func removeResource(obj interface{}) error {
return handleResource(obj, "Remove", DefaultDynamicAPIServer.RemoveResource)
}
func newDynamicResourceFromConfigMap(cm *corev1.ConfigMap) (obj ResourceProvider, err error) {
encoderTemplate, encoderExists := cm.Data[encoderKey]
decoderTemplate, decoderExists := cm.Data[decoderKey]
if !encoderExists || !decoderExists {
return nil, nil
}
obj, err = dynamicresource.NewDynamicResourceWithCodec(encoderTemplate, decoderTemplate)
return obj, err
}

View File

@ -0,0 +1,138 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver_test
import (
"context"
"fmt"
"path"
"strings"
"testing"
"time"
"github.com/emicklei/go-restful/v3"
"github.com/kubevela/pkg/meta"
"github.com/kubevela/pkg/util/k8s"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/exp/slices"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/discovery"
"k8s.io/apiserver/pkg/server"
"sigs.k8s.io/apiserver-runtime/pkg/builder"
"github.com/kubevela/pkg/util/singleton"
apiserver "github.com/kubevela/prism/pkg/dynamicapiserver"
_ "github.com/kubevela/prism/test/bootstrap"
)
func TestDynamicServer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Test Dynamic Server")
}
func createConfigMapForDiscovery(group, version, kind string) *corev1.ConfigMap {
cm := &corev1.ConfigMap{}
resource := strings.ToLower(kind) + "s"
cm.SetName(fmt.Sprintf("%s.%s.%s", resource, group, version))
cm.SetNamespace("vela-system")
cm.Data = map[string]string{}
cm.Data["encoder"] = fmt.Sprintf(`
parameter: {
apiVersion: "%s/%s"
kind: "%s"
metadata: {...}
}
output: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: parameter.metadata
data: {}
}
`, group, version, kind)
cm.Data["decoder"] = fmt.Sprintf(`
parameter: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: {...}
data: {...}
}
output: {
apiVersion: "%s/%s"
kind: "%s"
metadata: parameter.metadata
}
`, group, version, kind)
return cm
}
var _ = Describe("Test dynamic server", func() {
It("Test bootstrap and mutate spec", func() {
By("Bootstrap")
_ = k8s.EnsureNamespace(context.Background(), singleton.KubeClient.Get(), meta.NamespaceVelaSystem)
s := &builder.GenericAPIServer{
Handler: &server.APIServerHandler{
GoRestfulContainer: restful.NewContainer(),
},
DiscoveryGroupManager: discovery.NewRootAPIsHandler(nil, nil),
EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(),
}
singleton.InitGenericAPIServer(s)
cfg := &server.RecommendedConfig{}
singleton.InitServerConfig(cfg)
stopCh := make(chan struct{})
defer close(stopCh)
_ = apiserver.StartDefaultDynamicAPIServer(server.PostStartHookContext{StopCh: stopCh})
By("Add Resource API")
ctx := context.Background()
cm1 := createConfigMapForDiscovery("test.oam.dev", "v1alpha1", "Test")
Ω(singleton.KubeClient.Get().Create(ctx, cm1)).To(Succeed())
Eventually(func(g Gomega) {
g.Ω(slices.IndexFunc(s.Handler.GoRestfulContainer.RegisteredWebServices(), func(_ws *restful.WebService) bool {
return _ws.RootPath() == path.Join(server.APIGroupPrefix, "test.oam.dev", "v1alpha1")
}) >= 0).Should(BeTrue())
}).WithTimeout(5 * time.Second).WithPolling(1 * time.Second).Should(Succeed())
By("Update & Delete Resource API")
Ω(singleton.KubeClient.Get().Delete(ctx, cm1)).To(Succeed())
cm2 := createConfigMapForDiscovery("next.test.oam.dev", "v1alpha1", "Test")
Ω(singleton.KubeClient.Get().Create(ctx, cm2)).To(Succeed())
cm2.Data = createConfigMapForDiscovery("new.test.oam.dev", "v1alpha1", "Test").Data
Ω(singleton.KubeClient.Get().Update(ctx, cm2)).To(Succeed())
cm3 := createConfigMapForDiscovery("next.new.test.oam.dev", "v1alpha1", "Test")
Ω(singleton.KubeClient.Get().Create(ctx, cm3)).To(Succeed())
Ω(singleton.KubeClient.Get().Delete(ctx, cm3)).To(Succeed())
Eventually(func(g Gomega) {
webservices := s.Handler.GoRestfulContainer.RegisteredWebServices()
g.Ω(slices.IndexFunc(webservices, func(_ws *restful.WebService) bool {
return _ws.RootPath() == path.Join(server.APIGroupPrefix, "test.oam.dev", "v1alpha1")
}) < 0).Should(BeTrue())
g.Ω(slices.IndexFunc(webservices, func(_ws *restful.WebService) bool {
return _ws.RootPath() == path.Join(server.APIGroupPrefix, "next.test.oam.dev", "v1alpha1")
}) < 0).Should(BeTrue())
g.Ω(slices.IndexFunc(webservices, func(_ws *restful.WebService) bool {
return _ws.RootPath() == path.Join(server.APIGroupPrefix, "new.test.oam.dev", "v1alpha1")
}) >= 0).Should(BeTrue())
g.Ω(slices.IndexFunc(webservices, func(_ws *restful.WebService) bool {
return _ws.RootPath() == path.Join(server.APIGroupPrefix, "new.next.test.oam.dev", "v1alpha1")
}) < 0).Should(BeTrue())
}).WithTimeout(15 * time.Second).WithPolling(1 * time.Second).Should(Succeed())
})
})

View File

@ -1,49 +0,0 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"testing"
"github.com/stretchr/testify/require"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/utils/pointer"
)
func TestGetMetadataNameInFieldSelectorFromInternalVersionListOptions(t *testing.T) {
options := &metainternalversion.ListOptions{}
require.True(t, nil == GetMetadataNameInFieldSelectorFromInternalVersionListOptions(options))
options.FieldSelector = fields.SelectorFromSet(fields.Set{"metadata.name": "val"})
require.Equal(t, pointer.String("val"), GetMetadataNameInFieldSelectorFromInternalVersionListOptions(options))
}
func TestBuildQueryParamsFromLabelSelector(t *testing.T) {
sel := labels.NewSelector()
r1, err := labels.NewRequirement("a", selection.Equals, []string{"x"})
require.NoError(t, err)
r2, err := labels.NewRequirement("b", selection.In, []string{"y", "z"})
require.NoError(t, err)
r3, err := labels.NewRequirement("c", selection.NotEquals, []string{"t"})
require.NoError(t, err)
r4, err := labels.NewRequirement("d", selection.Equals, []string{"t"})
require.NoError(t, err)
sel = sel.Add(*r1, *r2, *r3, *r4)
require.Equal(t, "&a=x&b=y,z", BuildQueryParamsFromLabelSelector(sel, "a", "b"))
}

View File

@ -1,84 +0,0 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiserver
import (
"encoding/json"
"fmt"
"strings"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/utils/pointer"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// GetMetadataNameInFieldSelectorFromInternalVersionListOptions if fieldSelector is set in list options and metadata.name is specified
// return the name, otherwise nil
func GetMetadataNameInFieldSelectorFromInternalVersionListOptions(options *metainternalversion.ListOptions) *string {
if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() {
if name, found := options.FieldSelector.RequiresExactMatch("metadata.name"); found {
return pointer.String(name)
}
}
return nil
}
// NewMatchingLabelSelectorFromInternalVersionListOptions create MatchingLabelsSelector from InternalVersion ListOptions
func NewMatchingLabelSelectorFromInternalVersionListOptions(options *metainternalversion.ListOptions) client.MatchingLabelsSelector {
sel := labels.NewSelector()
if options != nil && options.LabelSelector != nil && !options.LabelSelector.Empty() {
sel = options.LabelSelector
}
return client.MatchingLabelsSelector{Selector: sel}
}
// NewListOptions create ListOptions from ListOption
func NewListOptions(options ...client.ListOption) *client.ListOptions {
opts := &client.ListOptions{}
for _, opt := range options {
opt.ApplyToList(opts)
}
return opts
}
// BuildQueryParamsFromLabelSelector build query params from label selector with provided keys
func BuildQueryParamsFromLabelSelector(sel labels.Selector, keys ...string) (params string) {
requirements, _ := sel.Requirements()
for _, r := range requirements {
if slices.Contains(keys, r.Key()) {
if r.Operator() == selection.Equals || r.Operator() == selection.In {
params += fmt.Sprintf("&%s=%s", r.Key(), strings.Join(r.Values().List(), ","))
}
}
}
return params
}
// GetStringFromRawExtension load string from raw extension
func GetStringFromRawExtension(data *runtime.RawExtension, path ...string) (val string) {
if data != nil && data.Raw != nil {
m := map[string]interface{}{}
_ = json.Unmarshal(data.Raw, &m)
val, _, _ = unstructured.NestedString(m, path...)
}
return val
}

View File

@ -1,68 +0,0 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package singleton
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
var kubeConfig *rest.Config
var kubeClient client.Client
var restMapper meta.RESTMapper
// GetKubeConfig get kubernetes config
func GetKubeConfig() *rest.Config {
return kubeConfig
}
// SetKubeConfig set kubernetes config
func SetKubeConfig(cfg *rest.Config) {
kubeConfig = cfg
}
// GetKubeClient get kubernetes client
func GetKubeClient() client.Client {
return kubeClient
}
// SetKubeClient set kubernetes client
func SetKubeClient(cli client.Client) {
kubeClient = cli
}
// InitLoopbackClient init clients
func InitLoopbackClient(ctx server.PostStartHookContext) (err error) {
if kubeConfig, err = config.GetConfig(); err != nil {
return err
}
if restMapper, err = apiutil.NewDiscoveryRESTMapper(kubeConfig); err != nil {
return err
}
if kubeClient, err = client.New(kubeConfig, client.Options{
Scheme: scheme.Scheme,
Mapper: restMapper,
}); err != nil {
return err
}
return nil
}

View File

@ -19,45 +19,13 @@ package bootstrap
import (
"path/filepath"
"runtime"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"github.com/kubevela/prism/pkg/util/singleton"
"github.com/kubevela/pkg/util/test/bootstrap"
)
var testEnv *envtest.Environment
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
By("Bootstrapping Test Environment")
_, file, _, _ := runtime.Caller(0)
crdPath := filepath.Join(filepath.Dir(filepath.Dir(file)), "testdata/crds")
testEnv = &envtest.Environment{
ControlPlaneStartTimeout: time.Minute,
ControlPlaneStopTimeout: time.Minute,
Scheme: scheme.Scheme,
CRDDirectoryPaths: []string{crdPath},
UseExistingCluster: pointer.Bool(false),
}
cfg, err := testEnv.Start()
Ω(err).To(Succeed())
singleton.SetKubeConfig(cfg)
k8sClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme})
Ω(err).To(Succeed())
singleton.SetKubeClient(k8sClient)
})
var _ = AfterSuite(func() {
By("Tearing Down the Test Environment")
Ω(testEnv.Stop()).To(Succeed())
})
var (
_, fp, _, _ = runtime.Caller(0)
_ = bootstrap.InitKubeBuilderForTest(
bootstrap.WithCRDPath(filepath.Join(filepath.Dir(fp), "../testdata/crds")),
)
)

View File

@ -1,46 +0,0 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"github.com/kubevela/prism/pkg/util/singleton"
)
// CreateNamespace .
func CreateNamespace(name string) error {
ns := &corev1.Namespace{}
ns.SetName(name)
return singleton.GetKubeClient().Create(context.Background(), ns)
}
// DeleteNamespace .
func DeleteNamespace(name string) error {
ns := &corev1.Namespace{}
if err := singleton.GetKubeClient().Get(context.Background(), types.NamespacedName{Name: name}, ns); err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
return singleton.GetKubeClient().Delete(context.Background(), ns)
}