mirror of https://github.com/kubevela/prism.git
Compare commits
36 Commits
v1.5.0-alp
...
master
Author | SHA1 | Date |
---|---|---|
|
eee4e07950 | |
|
1ecdbb4aca | |
|
01dd83251a | |
|
88d23063b9 | |
|
c12eae92d0 | |
|
dc809e6c77 | |
|
b7e45f7f84 | |
|
cad8574718 | |
|
946eb7a2ce | |
|
eee3f1c2cc | |
|
d67ab30eb9 | |
|
8026c15cec | |
|
51eb282528 | |
|
fd6779756c | |
|
2aca265923 | |
|
6e77b01d61 | |
|
c431248cb0 | |
|
bb878067ac | |
|
42496db81b | |
|
bff60a0474 | |
|
6a25625e53 | |
|
faad0deebb | |
|
5dd2da483c | |
|
9463fc48de | |
|
6bf3ad33f8 | |
|
c3fb485060 | |
|
c752b9d821 | |
|
0b780bccf0 | |
|
b343320c2e | |
|
b2d7fec4ef | |
|
c3c6d996c2 | |
|
245a784aeb | |
|
6268967fe5 | |
|
dfe1d1910f | |
|
3cbc86010c | |
|
bdcaaa8f3c |
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -12,7 +12,7 @@ on:
|
|||
- release-*
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.18'
|
||||
GO_VERSION: '1.19'
|
||||
|
||||
jobs:
|
||||
|
||||
|
|
96
README.md
96
README.md
|
@ -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
|
||||
|
||||

|
||||
|
||||
#### 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.
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
#### 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.
|
|
@ -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 }}
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,4 +41,7 @@ secureTLS:
|
|||
image:
|
||||
repository: oamdev/cluster-gateway
|
||||
tag: v1.4.0
|
||||
pullPolicy: IfNotPresent
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
dynamicAPI:
|
||||
enabled: true
|
|
@ -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"]
|
||||
|
|
|
@ -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
174
go.mod
|
@ -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
|
||||
)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
|
@ -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{}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
})
|
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
})
|
|
@ -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"))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")),
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue