mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-23 06:14:30 +00:00
Compare commits
54 Commits
v1.4.2
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35a84e9cbf | ||
|
|
bf251d5039 | ||
|
|
31f0b28d96 | ||
|
|
810c47545e | ||
|
|
293f38dd84 | ||
|
|
1cf2cd23d2 | ||
|
|
69cf083d4a | ||
|
|
7df2b34a0b | ||
|
|
2a03e16098 | ||
|
|
110927ed97 | ||
|
|
90fbfa0f81 | ||
|
|
7fb045328d | ||
|
|
766c5852c6 | ||
|
|
4cb9a14b18 | ||
|
|
9a1e75cf48 | ||
|
|
192dc8966d | ||
|
|
b596b70ebe | ||
|
|
0cd370e867 | ||
|
|
d9adc73e5c | ||
|
|
4a2d9807c8 | ||
|
|
840cb8ce58 | ||
|
|
5a64fec916 | ||
|
|
657a374ded | ||
|
|
dfe12cd9ca | ||
|
|
cd42f67848 | ||
|
|
61d2c588e3 | ||
|
|
b3dad698a5 | ||
|
|
ec5159c2ca | ||
|
|
a7b2b221e0 | ||
|
|
caa495a5d9 | ||
|
|
15bea4fb64 | ||
|
|
1a094a4eea | ||
|
|
65b6f47330 | ||
|
|
3a4cd2dca6 | ||
|
|
9fabd950e5 | ||
|
|
0a012b4d34 | ||
|
|
7c231e6c48 | ||
|
|
36b6c3e7b5 | ||
|
|
4cc019722c | ||
|
|
b040ae65da | ||
|
|
f0fb4ed099 | ||
|
|
7f89d12059 | ||
|
|
3c61bcb8f0 | ||
|
|
a14b536fd1 | ||
|
|
ba5a726854 | ||
|
|
ffb9d06427 | ||
|
|
819dc26ace | ||
|
|
5e3ab732df | ||
|
|
62d5507499 | ||
|
|
c0daf688a6 | ||
|
|
48d19a2427 | ||
|
|
4da8d49e60 | ||
|
|
4db9e89816 | ||
|
|
667053409d |
2
.github/workflows/apiserver-test.yaml
vendored
2
.github/workflows/apiserver-test.yaml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
|
||||
apiserver-unit-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
needs: [ detect-noop,set-k8s-matrix ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
|
||||
89
.github/workflows/chart.yaml
vendored
Normal file
89
.github/workflows/chart.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Publish Chart
|
||||
|
||||
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 }}
|
||||
ARTIFACT_HUB_REPOSITORY_ID: ${{ secrets.ARTIFACT_HUB_REPOSITORY_ID }}
|
||||
|
||||
jobs:
|
||||
publish-charts:
|
||||
env:
|
||||
HELM_CHARTS_DIR: charts
|
||||
HELM_CHART: charts/vela-core
|
||||
MINIMAL_HELM_CHART: charts/vela-minimal
|
||||
LEGACY_HELM_CHART: legacy/charts/vela-core-legacy
|
||||
VELA_ROLLOUT_HELM_CHART: runtime/rollout/charts
|
||||
LOCAL_OSS_DIRECTORY: .oss/
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Get git revision
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=git_revision::$(git rev-parse --short HEAD)"
|
||||
- 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'
|
||||
- name: Generate helm doc
|
||||
run: |
|
||||
make helm-doc-gen
|
||||
- name: Prepare legacy chart
|
||||
run: |
|
||||
rsync -r $LEGACY_HELM_CHART $HELM_CHARTS_DIR
|
||||
rsync -r $HELM_CHART/* $LEGACY_HELM_CHART --exclude=Chart.yaml --exclude=crds
|
||||
- name: Prepare vela chart
|
||||
run: |
|
||||
rsync -r $VELA_ROLLOUT_HELM_CHART $HELM_CHARTS_DIR
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
echo ::set-output name=VERSION::${VERSION}
|
||||
- name: Tag helm chart image
|
||||
run: |
|
||||
image_tag=${{ steps.get_version.outputs.VERSION }}
|
||||
chart_version=${{ steps.get_version.outputs.VERSION }}
|
||||
sed -i "s/latest/${image_tag}/g" $HELM_CHART/values.yaml
|
||||
sed -i "s/latest/${image_tag}/g" $MINIMAL_HELM_CHART/values.yaml
|
||||
sed -i "s/latest/${image_tag}/g" $LEGACY_HELM_CHART/values.yaml
|
||||
sed -i "s/latest/${image_tag}/g" $VELA_ROLLOUT_HELM_CHART/values.yaml
|
||||
chart_smever=${chart_version#"v"}
|
||||
sed -i "s/0.1.0/$chart_smever/g" $HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $MINIMAL_HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $LEGACY_HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $VELA_ROLLOUT_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/core $LOCAL_OSS_DIRECTORY
|
||||
- name: add artifacthub stuff to the repo
|
||||
run: |
|
||||
rsync $HELM_CHART/README.md $LEGACY_HELM_CHART/README.md
|
||||
rsync $HELM_CHART/README.md $VELA_ROLLOUT_HELM_CHART/README.md
|
||||
sed -i "s/ARTIFACT_HUB_REPOSITORY_ID/$ARTIFACT_HUB_REPOSITORY_ID/g" hack/artifacthub/artifacthub-repo.yml
|
||||
rsync hack/artifacthub/artifacthub-repo.yml $LOCAL_OSS_DIRECTORY
|
||||
- name: Package helm charts
|
||||
run: |
|
||||
helm package $HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $MINIMAL_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $LEGACY_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $VELA_ROLLOUT_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm repo index --url https://$BUCKET.$ENDPOINT/core $LOCAL_OSS_DIRECTORY
|
||||
- name: sync local to cloud
|
||||
run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/core -f
|
||||
6
.github/workflows/e2e-multicluster-test.yml
vendored
6
.github/workflows/e2e-multicluster-test.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
|
||||
e2e-multi-cluster-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
needs: [ detect-noop,set-k8s-matrix ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
@@ -97,7 +97,9 @@ jobs:
|
||||
kubectl cluster-info
|
||||
|
||||
- name: Load Image to kind cluster (Hub)
|
||||
run: make kind-load
|
||||
run: |
|
||||
make kind-load
|
||||
make kind-load-runtime-cluster
|
||||
|
||||
- name: Cleanup for e2e tests
|
||||
run: |
|
||||
|
||||
2
.github/workflows/e2e-rollout-test.yml
vendored
2
.github/workflows/e2e-rollout-test.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
fi
|
||||
|
||||
e2e-rollout-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
needs: [ detect-noop,set-k8s-matrix ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
|
||||
2
.github/workflows/e2e-test.yml
vendored
2
.github/workflows/e2e-test.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
fi
|
||||
|
||||
e2e-tests:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
needs: [ detect-noop,set-k8s-matrix ]
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
strategy:
|
||||
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
|
||||
check-diff:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
|
||||
103
.github/workflows/registry.yml
vendored
103
.github/workflows/registry.yml
vendored
@@ -8,11 +8,8 @@ on:
|
||||
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 }}
|
||||
ARTIFACT_HUB_REPOSITORY_ID: ${{ secrets.ARTIFACT_HUB_REPOSITORY_ID }}
|
||||
|
||||
jobs:
|
||||
publish-core-images:
|
||||
@@ -47,8 +44,8 @@ jobs:
|
||||
- name: Login Alibaba Cloud ACR
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: kubevela-registry.cn-hangzhou.cr.aliyuncs.com
|
||||
username: ${{ secrets.ACR_USERNAME }}@aliyun-inner.com
|
||||
registry: ${{ secrets.ACR_DOMAIN }}
|
||||
username: ${{ secrets.ACR_USERNAME }}
|
||||
password: ${{ secrets.ACR_PASSWORD }}
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
@@ -72,7 +69,7 @@ jobs:
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-core:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- uses: docker/build-push-action@v2
|
||||
name: Build & Pushing CLI for Dockerhub, GHCR and ACR
|
||||
@@ -91,7 +88,7 @@ jobs:
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-cli:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/oamdev/vela-cli:${{ steps.get_version.outputs.VERSION }}
|
||||
kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-cli:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-cli:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
publish-addon-images:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -125,8 +122,8 @@ jobs:
|
||||
- name: Login Alibaba Cloud ACR
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: kubevela-registry.cn-hangzhou.cr.aliyuncs.com
|
||||
username: ${{ secrets.ACR_USERNAME }}@aliyun-inner.com
|
||||
registry: ${{ secrets.ACR_DOMAIN }}
|
||||
username: ${{ secrets.ACR_USERNAME }}
|
||||
password: ${{ secrets.ACR_PASSWORD }}
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
@@ -150,7 +147,7 @@ jobs:
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-apiserver:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
- uses: docker/build-push-action@v2
|
||||
name: Build & Pushing runtime rollout Dockerhub, GHCR and ACR
|
||||
@@ -169,91 +166,7 @@ jobs:
|
||||
tags: |-
|
||||
docker.io/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
kubevela-registry.cn-hangzhou.cr.aliyuncs.com/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
publish-charts:
|
||||
env:
|
||||
HELM_CHARTS_DIR: charts
|
||||
HELM_CHART: charts/vela-core
|
||||
MINIMAL_HELM_CHART: charts/vela-minimal
|
||||
LEGACY_HELM_CHART: legacy/charts/vela-core-legacy
|
||||
VELA_ROLLOUT_HELM_CHART: runtime/rollout/charts
|
||||
LOCAL_OSS_DIRECTORY: .oss/
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Get git revision
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=git_revision::$(git rev-parse --short HEAD)"
|
||||
- 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'
|
||||
- name: Generate helm doc
|
||||
run: |
|
||||
make helm-doc-gen
|
||||
- name: Prepare legacy chart
|
||||
run: |
|
||||
rsync -r $LEGACY_HELM_CHART $HELM_CHARTS_DIR
|
||||
rsync -r $HELM_CHART/* $LEGACY_HELM_CHART --exclude=Chart.yaml --exclude=crds
|
||||
- name: Prepare vela chart
|
||||
run: |
|
||||
rsync -r $VELA_ROLLOUT_HELM_CHART $HELM_CHARTS_DIR
|
||||
- uses: oprypin/find-latest-tag@v1
|
||||
with:
|
||||
repository: oam-dev/kubevela
|
||||
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
|
||||
sed -i "s/latest/${image_tag}/g" $MINIMAL_HELM_CHART/values.yaml
|
||||
sed -i "s/latest/${image_tag}/g" $LEGACY_HELM_CHART/values.yaml
|
||||
sed -i "s/latest/${image_tag}/g" $VELA_ROLLOUT_HELM_CHART/values.yaml
|
||||
chart_smever=${chart_version#"v"}
|
||||
sed -i "s/0.1.0/$chart_smever/g" $HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $MINIMAL_HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $LEGACY_HELM_CHART/Chart.yaml
|
||||
sed -i "s/0.1.0/$chart_smever/g" $VELA_ROLLOUT_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/core $LOCAL_OSS_DIRECTORY
|
||||
- name: add artifacthub stuff to the repo
|
||||
run: |
|
||||
rsync $HELM_CHART/README.md $LEGACY_HELM_CHART/README.md
|
||||
rsync $HELM_CHART/README.md $VELA_ROLLOUT_HELM_CHART/README.md
|
||||
sed -i "s/ARTIFACT_HUB_REPOSITORY_ID/$ARTIFACT_HUB_REPOSITORY_ID/g" hack/artifacthub/artifacthub-repo.yml
|
||||
rsync hack/artifacthub/artifacthub-repo.yml $LOCAL_OSS_DIRECTORY
|
||||
- name: Package helm charts
|
||||
run: |
|
||||
helm package $HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $MINIMAL_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $LEGACY_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm package $VELA_ROLLOUT_HELM_CHART --destination $LOCAL_OSS_DIRECTORY
|
||||
helm repo index --url https://$BUCKET.$ENDPOINT/core $LOCAL_OSS_DIRECTORY
|
||||
- name: sync local to cloud
|
||||
run: ./ossutil --config-file .ossutilconfig sync $LOCAL_OSS_DIRECTORY oss://$BUCKET/core -f
|
||||
${{ secrets.ACR_DOMAIN }}/oamdev/vela-rollout:${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
publish-capabilities:
|
||||
env:
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -123,6 +123,11 @@ jobs:
|
||||
- name: sync the latest version file
|
||||
if: ${{ !contains(env.VELA_VERSION,'alpha') && !contains(env.VELA_VERSION,'beta') }}
|
||||
run: |
|
||||
LATEST_VERSION=$(curl -fsSl https://static.kubevela.net/binary/vela/latest_version)
|
||||
verlte() {
|
||||
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
|
||||
}
|
||||
verlte ${{ env.VELA_VERSION }} $LATEST_VERSION && echo "${{ env.VELA_VERSION }} <= $LATEST_VERSION, skip update" && exit 0
|
||||
echo ${{ env.VELA_VERSION }} > ./latest_version
|
||||
./ossutil --config-file .ossutilconfig cp -u ./latest_version oss://$BUCKET/binary/vela/latest_version
|
||||
|
||||
|
||||
2
.github/workflows/timed-task.yml
vendored
2
.github/workflows/timed-task.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
- cron: '* * * * *'
|
||||
jobs:
|
||||
clean-image:
|
||||
runs-on: aliyun
|
||||
runs-on: aliyun-legacy
|
||||
steps:
|
||||
- name: Cleanup image
|
||||
run: docker image prune -f
|
||||
6
Makefile
6
Makefile
@@ -83,15 +83,17 @@ endif
|
||||
|
||||
|
||||
# load docker image to the kind cluster
|
||||
kind-load: kind-load-runtime-cluster
|
||||
kind-load: kind-load-rollout
|
||||
docker build -t $(VELA_CORE_TEST_IMAGE) -f Dockerfile.e2e .
|
||||
kind load docker-image $(VELA_CORE_TEST_IMAGE) || { echo >&2 "kind not installed or error loading image: $(VELA_CORE_TEST_IMAGE)"; exit 1; }
|
||||
|
||||
kind-load-runtime-cluster:
|
||||
kind-load-rollout:
|
||||
/bin/sh hack/e2e/build_runtime_rollout.sh
|
||||
docker build -t $(VELA_RUNTIME_ROLLOUT_TEST_IMAGE) -f runtime/rollout/e2e/Dockerfile.e2e runtime/rollout/e2e/
|
||||
rm -rf runtime/rollout/e2e/tmp
|
||||
kind load docker-image $(VELA_RUNTIME_ROLLOUT_TEST_IMAGE) || { echo >&2 "kind not installed or error loading image: $(VELA_RUNTIME_ROLLOUT_TEST_IMAGE)"; exit 1; }
|
||||
|
||||
kind-load-runtime-cluster:
|
||||
kind load docker-image $(VELA_RUNTIME_ROLLOUT_TEST_IMAGE) --name=$(RUNTIME_CLUSTER_NAME) || { echo >&2 "kind not installed or error loading image: $(VELA_RUNTIME_ROLLOUT_TEST_IMAGE)"; exit 1; }
|
||||
|
||||
# Run tests
|
||||
|
||||
@@ -106,7 +106,7 @@ metadata:
|
||||
name: v1alpha1.cluster.core.oam.dev
|
||||
annotations:
|
||||
{{- if and .Values.multicluster.clusterGateway.secureTLS.enabled .Values.multicluster.clusterGateway.secureTLS.certManager.enabled }}
|
||||
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kubevela.fullname" . }}-cluster-gateway-tls"
|
||||
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ template "kubevela.fullname" . }}-cluster-gateway-tls-v2"
|
||||
{{- end }}
|
||||
labels:
|
||||
api: cluster-extension-apiserver
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: affinity specify affinity and tolerationon K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
definition.oam.dev/description: Affinity specifies affinity and toleration K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: affinity
|
||||
|
||||
@@ -20,6 +20,7 @@ spec:
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
output: {
|
||||
@@ -42,21 +43,29 @@ spec:
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
@@ -67,6 +76,10 @@ spec:
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
@@ -196,14 +196,14 @@ spec:
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
secretKeyRef?: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Selects a key of a config map in the pod's namespace
|
||||
configMapKeyRef: {
|
||||
configMapKeyRef?: {
|
||||
// +usage=The name of the config map in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
volumeMounts: [{
|
||||
name: parameter.mountName
|
||||
mountPath: parameter.initMountPath
|
||||
}]
|
||||
}] + parameter.extraVolumeMounts
|
||||
}]
|
||||
// +patchKey=name
|
||||
volumes: [{
|
||||
@@ -97,5 +97,13 @@ spec:
|
||||
|
||||
// +usage=Specify the mount path of init container
|
||||
initMountPath: string
|
||||
|
||||
// +usage=Specify the extra volume mounts for the init container
|
||||
extraVolumeMounts: [...{
|
||||
// +usage=The name of the volume to be mounted
|
||||
name: string
|
||||
// +usage=The mountPath for mount in the init container
|
||||
mountPath: string
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,18 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
#K8sObject: {
|
||||
apiVersion: string
|
||||
kind: string
|
||||
metadata: {
|
||||
name: string
|
||||
...
|
||||
}
|
||||
// +usage=The resource type for the Kubernetes objects
|
||||
resource?: string
|
||||
// +usage=The group name for the Kubernetes objects
|
||||
group?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects with the name, exclusive to labelSelector
|
||||
name?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects from the namespace. Otherwise, fetch from the application's namespace.
|
||||
namespace?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects from the cluster. Otherwise, fetch from the local cluster.
|
||||
cluster?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects according to the label selector, exclusive to name
|
||||
labelSelector?: [string]: string
|
||||
...
|
||||
}
|
||||
output: parameter.objects[0]
|
||||
@@ -30,7 +36,12 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: objects: [...#K8sObject]
|
||||
parameter: {
|
||||
// +usage=If specified, application will fetch native Kubernetes objects according to the object description
|
||||
objects?: [...#K8sObject]
|
||||
// +usage=If specified, the objects in the urls will be loaded.
|
||||
urls?: [...string]
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
|
||||
@@ -14,10 +14,114 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
#Privileges: {
|
||||
// +usage=Specify the verbs to be allowed for the resource
|
||||
verbs: [...string]
|
||||
// +usage=Specify the apiGroups of the resource
|
||||
apiGroups?: [...string]
|
||||
// +usage=Specify the resources to be allowed
|
||||
resources?: [...string]
|
||||
// +usage=Specify the resourceNames to be allowed
|
||||
resourceNames?: [...string]
|
||||
// +usage=Specify the resource url to be allowed
|
||||
nonResourceURLs?: [...string]
|
||||
// +usage=Specify the scope of the privileges, default to be namespace scope
|
||||
scope: *"namespace" | "cluster"
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the name of ServiceAccount
|
||||
name: string
|
||||
// +usage=Specify whether to create new ServiceAccount or not
|
||||
create: *false | bool
|
||||
// +usage=Specify the privileges of the ServiceAccount, if not empty, RoleBindings(ClusterRoleBindings) will be created
|
||||
privileges?: [...#Privileges]
|
||||
}
|
||||
// +patchStrategy=retainKeys
|
||||
patch: spec: template: spec: serviceAccountName: parameter.name
|
||||
_clusterPrivileges: [ for p in parameter.privileges if p.scope == "cluster" {p}]
|
||||
_namespacePrivileges: [ for p in parameter.privileges if p.scope == "namespace" {p}]
|
||||
outputs: {
|
||||
if parameter.create {
|
||||
"service-account": {
|
||||
apiVersion: "v1"
|
||||
kind: "ServiceAccount"
|
||||
metadata: name: parameter.name
|
||||
}
|
||||
}
|
||||
if parameter.privileges != _|_ {
|
||||
if len(_clusterPrivileges) > 0 {
|
||||
"cluster-role": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "ClusterRole"
|
||||
metadata: name: "\(context.namespace):\(parameter.name)"
|
||||
rules: [ for p in _clusterPrivileges {
|
||||
verbs: p.verbs
|
||||
if p.apiGroups != _|_ {
|
||||
apiGroups: p.apiGroups
|
||||
}
|
||||
if p.resources != _|_ {
|
||||
resources: p.resources
|
||||
}
|
||||
if p.resourceNames != _|_ {
|
||||
resourceNames: p.resourceNames
|
||||
}
|
||||
if p.nonResourceURLs != _|_ {
|
||||
nonResourceURLs: p.nonResourceURLs
|
||||
}
|
||||
}]
|
||||
}
|
||||
"cluster-role-binding": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "ClusterRoleBinding"
|
||||
metadata: name: "\(context.namespace):\(parameter.name)"
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "ClusterRole"
|
||||
name: "\(context.namespace):\(parameter.name)"
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: parameter.name
|
||||
namespace: "\(context.namespace)"
|
||||
}]
|
||||
}
|
||||
}
|
||||
if len(_namespacePrivileges) > 0 {
|
||||
role: {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "Role"
|
||||
metadata: name: parameter.name
|
||||
rules: [ for p in _namespacePrivileges {
|
||||
verbs: p.verbs
|
||||
if p.apiGroups != _|_ {
|
||||
apiGroups: p.apiGroups
|
||||
}
|
||||
if p.resources != _|_ {
|
||||
resources: p.resources
|
||||
}
|
||||
if p.resourceNames != _|_ {
|
||||
resourceNames: p.resourceNames
|
||||
}
|
||||
if p.nonResourceURLs != _|_ {
|
||||
nonResourceURLs: p.nonResourceURLs
|
||||
}
|
||||
}]
|
||||
}
|
||||
"role-binding": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "RoleBinding"
|
||||
metadata: name: parameter.name
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "Role"
|
||||
name: parameter.name
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: parameter.name
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,11 @@ spec:
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Specify the field reference for env
|
||||
fieldRef?: {
|
||||
// +usage=Specify the field path for env
|
||||
fieldPath: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ spec:
|
||||
{
|
||||
name: "pvc-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -73,6 +76,9 @@ spec:
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -103,6 +109,9 @@ spec:
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -133,6 +142,9 @@ spec:
|
||||
{
|
||||
name: "emptydir-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -141,12 +153,28 @@ spec:
|
||||
{
|
||||
name: "pvc-" + v.name
|
||||
devicePath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
volumesList: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
deDupVolumesArray: [
|
||||
for val in [
|
||||
for i, vi in volumesList {
|
||||
for j, vj in volumesList if j < i && vi.name == vj.name {
|
||||
_ignore: true
|
||||
}
|
||||
vi
|
||||
},
|
||||
] if val._ignore == _|_ {
|
||||
val
|
||||
},
|
||||
]
|
||||
patch: spec: template: spec: {
|
||||
// +patchKey=name
|
||||
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
volumes: deDupVolumesArray
|
||||
|
||||
containers: [{
|
||||
// +patchKey=name
|
||||
@@ -234,6 +262,7 @@ spec:
|
||||
name: string
|
||||
mountOnly: *false | bool
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
volumeMode: *"Filesystem" | string
|
||||
volumeName?: string
|
||||
accessModes: *["ReadWriteOnce"] | [...string]
|
||||
@@ -275,6 +304,7 @@ spec:
|
||||
configMapKey: string
|
||||
}]
|
||||
mountPath?: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
data?: {...}
|
||||
@@ -298,6 +328,7 @@ spec:
|
||||
secretKey: string
|
||||
}]
|
||||
mountPath?: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
stringData?: {...}
|
||||
@@ -313,6 +344,7 @@ spec:
|
||||
emptyDir?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
medium: *"" | "Memory"
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -149,14 +149,14 @@ spec:
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
secretKeyRef?: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Selects a key of a config map in the pod's namespace
|
||||
configMapKeyRef: {
|
||||
configMapKeyRef?: {
|
||||
// +usage=The name of the config map in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
|
||||
@@ -20,7 +20,10 @@ spec:
|
||||
for v in parameter.volumeMounts.pvc {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -29,7 +32,10 @@ spec:
|
||||
for v in parameter.volumeMounts.configMap {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -38,7 +44,10 @@ spec:
|
||||
for v in parameter.volumeMounts.secret {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -47,7 +56,10 @@ spec:
|
||||
for v in parameter.volumeMounts.emptyDir {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -56,7 +68,10 @@ spec:
|
||||
for v in parameter.volumeMounts.hostPath {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -119,6 +134,19 @@ spec:
|
||||
},
|
||||
] | []
|
||||
}
|
||||
volumesList: volumesArray.pvc + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir + volumesArray.hostPath
|
||||
deDupVolumesArray: [
|
||||
for val in [
|
||||
for i, vi in volumesList {
|
||||
for j, vj in volumesList if j < i && vi.name == vj.name {
|
||||
_ignore: true
|
||||
}
|
||||
vi
|
||||
},
|
||||
] if val._ignore == _|_ {
|
||||
val
|
||||
},
|
||||
]
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
@@ -262,7 +290,7 @@ spec:
|
||||
}
|
||||
|
||||
if parameter["volumeMounts"] != _|_ {
|
||||
volumes: volumesArray.pvc + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir + volumesArray.hostPath
|
||||
volumes: deDupVolumesArray
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,6 +403,7 @@ spec:
|
||||
pvc?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
// +usage=The name of the PVC
|
||||
claimName: string
|
||||
}]
|
||||
@@ -382,6 +411,7 @@ spec:
|
||||
configMap?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
cmName: string
|
||||
items?: [...{
|
||||
@@ -394,6 +424,7 @@ spec:
|
||||
secret?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
secretName: string
|
||||
items?: [...{
|
||||
@@ -406,12 +437,14 @@ spec:
|
||||
emptyDir?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
medium: *"" | "Memory"
|
||||
}]
|
||||
// +usage=Mount HostPath type volume
|
||||
hostPath?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
path: string
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ metadata:
|
||||
name: {{ include "kubevela.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
controller.oam.dev/name: vela-core
|
||||
{{- include "kubevela.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
|
||||
@@ -4,7 +4,7 @@ apiVersion: core.oam.dev/v1beta1
|
||||
kind: TraitDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: affinity specify affinity and tolerationon K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
definition.oam.dev/description: Affinity specifies affinity and toleration K8s pod for your workload which follows the pod spec in path 'spec.template'.
|
||||
labels:
|
||||
custom.definition.oam.dev/ui-hidden: "true"
|
||||
name: affinity
|
||||
|
||||
@@ -20,6 +20,7 @@ spec:
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
output: {
|
||||
@@ -42,21 +43,29 @@ spec:
|
||||
if parameter.auth == _|_ {
|
||||
type: "Opaque"
|
||||
}
|
||||
if parameter.auth != _|_ {
|
||||
stringData: ".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
stringData: {
|
||||
if parameter.auth != _|_ && parameter.auth.username != _|_ {
|
||||
".dockerconfigjson": json.Marshal({
|
||||
auths: "\(parameter.registry)": {
|
||||
username: parameter.auth.username
|
||||
password: parameter.auth.password
|
||||
if parameter.auth.email != _|_ {
|
||||
email: parameter.auth.email
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
if parameter.insecure != _|_ {
|
||||
"insecure-skip-verify": strconv.FormatBool(parameter.insecure)
|
||||
}
|
||||
if parameter.useHTTP != _|_ {
|
||||
"protocol-use-http": strconv.FormatBool(parameter.useHTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Image registry FQDN
|
||||
// +usage=Image registry FQDN, such as: index.docker.io
|
||||
registry: string
|
||||
// +usage=Authenticate the image registry
|
||||
auth?: {
|
||||
@@ -67,6 +76,10 @@ spec:
|
||||
// +usage=Private Image registry email
|
||||
email?: string
|
||||
}
|
||||
// +usage=For the registry server that uses the self-signed certificate
|
||||
insecure?: bool
|
||||
// +usage=For the registry server that uses the HTTP protocol
|
||||
useHTTP?: bool
|
||||
}
|
||||
workload:
|
||||
type: autodetects.core.oam.dev
|
||||
|
||||
@@ -196,14 +196,14 @@ spec:
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
secretKeyRef?: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Selects a key of a config map in the pod's namespace
|
||||
configMapKeyRef: {
|
||||
configMapKeyRef?: {
|
||||
// +usage=The name of the config map in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
volumeMounts: [{
|
||||
name: parameter.mountName
|
||||
mountPath: parameter.initMountPath
|
||||
}]
|
||||
}] + parameter.extraVolumeMounts
|
||||
}]
|
||||
// +patchKey=name
|
||||
volumes: [{
|
||||
@@ -97,5 +97,13 @@ spec:
|
||||
|
||||
// +usage=Specify the mount path of init container
|
||||
initMountPath: string
|
||||
|
||||
// +usage=Specify the extra volume mounts for the init container
|
||||
extraVolumeMounts: [...{
|
||||
// +usage=The name of the volume to be mounted
|
||||
name: string
|
||||
// +usage=The mountPath for mount in the init container
|
||||
mountPath: string
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,18 @@ spec:
|
||||
cue:
|
||||
template: |
|
||||
#K8sObject: {
|
||||
apiVersion: string
|
||||
kind: string
|
||||
metadata: {
|
||||
name: string
|
||||
...
|
||||
}
|
||||
// +usage=The resource type for the Kubernetes objects
|
||||
resource?: string
|
||||
// +usage=The group name for the Kubernetes objects
|
||||
group?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects with the name, exclusive to labelSelector
|
||||
name?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects from the namespace. Otherwise, fetch from the application's namespace.
|
||||
namespace?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects from the cluster. Otherwise, fetch from the local cluster.
|
||||
cluster?: string
|
||||
// +usage=If specified, fetch the Kubernetes objects according to the label selector, exclusive to name
|
||||
labelSelector?: [string]: string
|
||||
...
|
||||
}
|
||||
output: parameter.objects[0]
|
||||
@@ -30,7 +36,12 @@ spec:
|
||||
}
|
||||
}
|
||||
}
|
||||
parameter: objects: [...#K8sObject]
|
||||
parameter: {
|
||||
// +usage=If specified, application will fetch native Kubernetes objects according to the object description
|
||||
objects?: [...#K8sObject]
|
||||
// +usage=If specified, the objects in the urls will be loaded.
|
||||
urls?: [...string]
|
||||
}
|
||||
status:
|
||||
customStatus: |-
|
||||
if context.output.apiVersion == "apps/v1" && context.output.kind == "Deployment" {
|
||||
|
||||
@@ -14,10 +14,114 @@ spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: |
|
||||
#Privileges: {
|
||||
// +usage=Specify the verbs to be allowed for the resource
|
||||
verbs: [...string]
|
||||
// +usage=Specify the apiGroups of the resource
|
||||
apiGroups?: [...string]
|
||||
// +usage=Specify the resources to be allowed
|
||||
resources?: [...string]
|
||||
// +usage=Specify the resourceNames to be allowed
|
||||
resourceNames?: [...string]
|
||||
// +usage=Specify the resource url to be allowed
|
||||
nonResourceURLs?: [...string]
|
||||
// +usage=Specify the scope of the privileges, default to be namespace scope
|
||||
scope: *"namespace" | "cluster"
|
||||
}
|
||||
parameter: {
|
||||
// +usage=Specify the name of ServiceAccount
|
||||
name: string
|
||||
// +usage=Specify whether to create new ServiceAccount or not
|
||||
create: *false | bool
|
||||
// +usage=Specify the privileges of the ServiceAccount, if not empty, RoleBindings(ClusterRoleBindings) will be created
|
||||
privileges?: [...#Privileges]
|
||||
}
|
||||
// +patchStrategy=retainKeys
|
||||
patch: spec: template: spec: serviceAccountName: parameter.name
|
||||
_clusterPrivileges: [ for p in parameter.privileges if p.scope == "cluster" {p}]
|
||||
_namespacePrivileges: [ for p in parameter.privileges if p.scope == "namespace" {p}]
|
||||
outputs: {
|
||||
if parameter.create {
|
||||
"service-account": {
|
||||
apiVersion: "v1"
|
||||
kind: "ServiceAccount"
|
||||
metadata: name: parameter.name
|
||||
}
|
||||
}
|
||||
if parameter.privileges != _|_ {
|
||||
if len(_clusterPrivileges) > 0 {
|
||||
"cluster-role": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "ClusterRole"
|
||||
metadata: name: "\(context.namespace):\(parameter.name)"
|
||||
rules: [ for p in _clusterPrivileges {
|
||||
verbs: p.verbs
|
||||
if p.apiGroups != _|_ {
|
||||
apiGroups: p.apiGroups
|
||||
}
|
||||
if p.resources != _|_ {
|
||||
resources: p.resources
|
||||
}
|
||||
if p.resourceNames != _|_ {
|
||||
resourceNames: p.resourceNames
|
||||
}
|
||||
if p.nonResourceURLs != _|_ {
|
||||
nonResourceURLs: p.nonResourceURLs
|
||||
}
|
||||
}]
|
||||
}
|
||||
"cluster-role-binding": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "ClusterRoleBinding"
|
||||
metadata: name: "\(context.namespace):\(parameter.name)"
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "ClusterRole"
|
||||
name: "\(context.namespace):\(parameter.name)"
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: parameter.name
|
||||
namespace: "\(context.namespace)"
|
||||
}]
|
||||
}
|
||||
}
|
||||
if len(_namespacePrivileges) > 0 {
|
||||
role: {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "Role"
|
||||
metadata: name: parameter.name
|
||||
rules: [ for p in _namespacePrivileges {
|
||||
verbs: p.verbs
|
||||
if p.apiGroups != _|_ {
|
||||
apiGroups: p.apiGroups
|
||||
}
|
||||
if p.resources != _|_ {
|
||||
resources: p.resources
|
||||
}
|
||||
if p.resourceNames != _|_ {
|
||||
resourceNames: p.resourceNames
|
||||
}
|
||||
if p.nonResourceURLs != _|_ {
|
||||
nonResourceURLs: p.nonResourceURLs
|
||||
}
|
||||
}]
|
||||
}
|
||||
"role-binding": {
|
||||
apiVersion: "rbac.authorization.k8s.io/v1"
|
||||
kind: "RoleBinding"
|
||||
metadata: name: parameter.name
|
||||
roleRef: {
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
kind: "Role"
|
||||
name: parameter.name
|
||||
}
|
||||
subjects: [{
|
||||
kind: "ServiceAccount"
|
||||
name: parameter.name
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,11 @@ spec:
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Specify the field reference for env
|
||||
fieldRef?: {
|
||||
// +usage=Specify the field path for env
|
||||
fieldPath: string
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ spec:
|
||||
{
|
||||
name: "pvc-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -73,6 +76,9 @@ spec:
|
||||
{
|
||||
name: "configmap-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -103,6 +109,9 @@ spec:
|
||||
{
|
||||
name: "secret-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -133,6 +142,9 @@ spec:
|
||||
{
|
||||
name: "emptydir-" + v.name
|
||||
mountPath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -141,12 +153,28 @@ spec:
|
||||
{
|
||||
name: "pvc-" + v.name
|
||||
devicePath: v.mountPath
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
}
|
||||
},
|
||||
] | []
|
||||
volumesList: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
deDupVolumesArray: [
|
||||
for val in [
|
||||
for i, vi in volumesList {
|
||||
for j, vj in volumesList if j < i && vi.name == vj.name {
|
||||
_ignore: true
|
||||
}
|
||||
vi
|
||||
},
|
||||
] if val._ignore == _|_ {
|
||||
val
|
||||
},
|
||||
]
|
||||
patch: spec: template: spec: {
|
||||
// +patchKey=name
|
||||
volumes: pvcVolumesList + configMapVolumesList + secretVolumesList + emptyDirVolumesList
|
||||
volumes: deDupVolumesArray
|
||||
|
||||
containers: [{
|
||||
// +patchKey=name
|
||||
@@ -234,6 +262,7 @@ spec:
|
||||
name: string
|
||||
mountOnly: *false | bool
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
volumeMode: *"Filesystem" | string
|
||||
volumeName?: string
|
||||
accessModes: *["ReadWriteOnce"] | [...string]
|
||||
@@ -275,6 +304,7 @@ spec:
|
||||
configMapKey: string
|
||||
}]
|
||||
mountPath?: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
data?: {...}
|
||||
@@ -298,6 +328,7 @@ spec:
|
||||
secretKey: string
|
||||
}]
|
||||
mountPath?: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
readOnly: *false | bool
|
||||
stringData?: {...}
|
||||
@@ -313,6 +344,7 @@ spec:
|
||||
emptyDir?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
medium: *"" | "Memory"
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -149,14 +149,14 @@ spec:
|
||||
// +usage=Specifies a source the value of this var should come from
|
||||
valueFrom?: {
|
||||
// +usage=Selects a key of a secret in the pod's namespace
|
||||
secretKeyRef: {
|
||||
secretKeyRef?: {
|
||||
// +usage=The name of the secret in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the secret to select from. Must be a valid secret key
|
||||
key: string
|
||||
}
|
||||
// +usage=Selects a key of a config map in the pod's namespace
|
||||
configMapKeyRef: {
|
||||
configMapKeyRef?: {
|
||||
// +usage=The name of the config map in the pod's namespace to select from
|
||||
name: string
|
||||
// +usage=The key of the config map to select from. Must be a valid secret key
|
||||
|
||||
@@ -20,7 +20,10 @@ spec:
|
||||
for v in parameter.volumeMounts.pvc {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -29,7 +32,10 @@ spec:
|
||||
for v in parameter.volumeMounts.configMap {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -38,7 +44,10 @@ spec:
|
||||
for v in parameter.volumeMounts.secret {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -47,7 +56,10 @@ spec:
|
||||
for v in parameter.volumeMounts.emptyDir {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -56,7 +68,10 @@ spec:
|
||||
for v in parameter.volumeMounts.hostPath {
|
||||
{
|
||||
mountPath: v.mountPath
|
||||
name: v.name
|
||||
if v.subPath != _|_ {
|
||||
subPath: v.subPath
|
||||
}
|
||||
name: v.name
|
||||
}
|
||||
},
|
||||
] | []
|
||||
@@ -119,6 +134,19 @@ spec:
|
||||
},
|
||||
] | []
|
||||
}
|
||||
volumesList: volumesArray.pvc + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir + volumesArray.hostPath
|
||||
deDupVolumesArray: [
|
||||
for val in [
|
||||
for i, vi in volumesList {
|
||||
for j, vj in volumesList if j < i && vi.name == vj.name {
|
||||
_ignore: true
|
||||
}
|
||||
vi
|
||||
},
|
||||
] if val._ignore == _|_ {
|
||||
val
|
||||
},
|
||||
]
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
@@ -262,7 +290,7 @@ spec:
|
||||
}
|
||||
|
||||
if parameter["volumeMounts"] != _|_ {
|
||||
volumes: volumesArray.pvc + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir + volumesArray.hostPath
|
||||
volumes: deDupVolumesArray
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,6 +403,7 @@ spec:
|
||||
pvc?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
// +usage=The name of the PVC
|
||||
claimName: string
|
||||
}]
|
||||
@@ -382,6 +411,7 @@ spec:
|
||||
configMap?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
cmName: string
|
||||
items?: [...{
|
||||
@@ -394,6 +424,7 @@ spec:
|
||||
secret?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
defaultMode: *420 | int
|
||||
secretName: string
|
||||
items?: [...{
|
||||
@@ -406,12 +437,14 @@ spec:
|
||||
emptyDir?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
medium: *"" | "Memory"
|
||||
}]
|
||||
// +usage=Mount HostPath type volume
|
||||
hostPath?: [...{
|
||||
name: string
|
||||
mountPath: string
|
||||
subPath?: string
|
||||
path: string
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ metadata:
|
||||
name: {{ include "kubevela.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
controller.oam.dev/name: vela-core
|
||||
{{- include "kubevela.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
|
||||
@@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
goflag "flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -138,6 +139,7 @@ func main() {
|
||||
flag.DurationVar(&clusterMetricsInterval, "cluster-metrics-interval", 15*time.Second, "The interval that ClusterMetricsMgr will collect metrics from clusters, default value is 15 seconds.")
|
||||
flag.BoolVar(&controllerArgs.EnableCompatibility, "enable-asi-compatibility", false, "enable compatibility for asi")
|
||||
flag.BoolVar(&controllerArgs.IgnoreAppWithoutControllerRequirement, "ignore-app-without-controller-version", false, "If true, application controller will not process the app without 'app.oam.dev/controller-version-require' annotation")
|
||||
flag.BoolVar(&controllerArgs.IgnoreDefinitionWithoutControllerRequirement, "ignore-definition-without-controller-version", false, "If true, trait/component/workflowstep definition controller will not process the definition without 'definition.oam.dev/controller-version-require' annotation")
|
||||
standardcontroller.AddOptimizeFlags()
|
||||
standardcontroller.AddAdmissionFlags()
|
||||
flag.IntVar(&resourcekeeper.MaxDispatchConcurrent, "max-dispatch-concurrent", 10, "Set the max dispatch concurrent number, default is 10")
|
||||
@@ -146,9 +148,10 @@ func main() {
|
||||
flag.IntVar(&custom.MaxWorkflowStepErrorRetryTimes, "max-workflow-step-error-retry-times", 10, "Set the max workflow step error retry times, default is 10")
|
||||
utilfeature.DefaultMutableFeatureGate.AddFlag(flag.CommandLine)
|
||||
|
||||
flag.Parse()
|
||||
// setup logging
|
||||
klog.InitFlags(nil)
|
||||
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||
flag.Parse()
|
||||
if logDebug {
|
||||
_ = flag.Set("v", strconv.Itoa(int(commonconfig.LogDebug)))
|
||||
}
|
||||
|
||||
@@ -25,3 +25,7 @@ spec:
|
||||
- name: my-mount
|
||||
mountPath: /test
|
||||
claimName: myclaim
|
||||
- name: my-mount
|
||||
mountPath: /test2
|
||||
subPath: /sub
|
||||
claimName: myclaim
|
||||
|
||||
@@ -16,8 +16,9 @@ spec:
|
||||
pvc:
|
||||
- name: test1
|
||||
mountPath: /test/mount/pvc
|
||||
- name: test2
|
||||
- name: test1
|
||||
mountPath: /test/mount2/pvc
|
||||
subPath: /sub
|
||||
configMap:
|
||||
- name: test1
|
||||
mountPath: /test/mount/cm
|
||||
|
||||
6
go.mod
6
go.mod
@@ -46,7 +46,7 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/kubevela/prism v1.4.0
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1
|
||||
github.com/oam-dev/cluster-gateway v1.4.0
|
||||
@@ -281,9 +281,9 @@ require (
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
|
||||
10
go.sum
10
go.sum
@@ -1344,8 +1344,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kubevela/prism v1.4.0 h1:wYCKXA3p9YpkcSsZjGnSEGBVL+3bPoZNEt4DYs3IxW4=
|
||||
github.com/kubevela/prism v1.4.0/go.mod h1:RP69+bRb57Occer6BeeF5zK3hrD1IhnYf2RNRsIdh9E=
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2 h1:TaHlO4raKI3ehVSYY8QixYMHdI0VwKHY1KPNWcUre3I=
|
||||
github.com/kubevela/prism v1.4.1-0.20220613123457-94f1190f87c2/go.mod h1:RP69+bRb57Occer6BeeF5zK3hrD1IhnYf2RNRsIdh9E=
|
||||
github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U=
|
||||
github.com/kunwardeep/paralleltest v1.0.2/go.mod h1:ZPqNm1fVHPllh5LPVujzbVz1JN2GhLxSfY+oqUsvG30=
|
||||
github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4=
|
||||
@@ -2371,8 +2371,9 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
|
||||
golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -2557,8 +2558,9 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -77,7 +77,7 @@ ifeq (, $(shell which readme-generator))
|
||||
@{ \
|
||||
set -e ;\
|
||||
echo 'installing readme-generator-for-helm' ;\
|
||||
npm install -g readme-generator-for-helm ;\
|
||||
npm install -g @bitnami/readme-generator-for-helm ;\
|
||||
}
|
||||
else
|
||||
@$(OK) readme-generator-for-helm is already installed
|
||||
|
||||
@@ -1058,21 +1058,22 @@ func Convert2SecName(name string) string {
|
||||
|
||||
// Installer helps addon enable, dependency-check, dispatch resources
|
||||
type Installer struct {
|
||||
ctx context.Context
|
||||
config *rest.Config
|
||||
addon *InstallPackage
|
||||
cli client.Client
|
||||
apply apply.Applicator
|
||||
r *Registry
|
||||
registryMeta map[string]SourceMeta
|
||||
args map[string]interface{}
|
||||
cache *Cache
|
||||
dc *discovery.DiscoveryClient
|
||||
ctx context.Context
|
||||
config *rest.Config
|
||||
addon *InstallPackage
|
||||
cli client.Client
|
||||
apply apply.Applicator
|
||||
r *Registry
|
||||
registryMeta map[string]SourceMeta
|
||||
args map[string]interface{}
|
||||
cache *Cache
|
||||
dc *discovery.DiscoveryClient
|
||||
skipVersionValidate bool
|
||||
}
|
||||
|
||||
// NewAddonInstaller will create an installer for addon
|
||||
func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache) Installer {
|
||||
return Installer{
|
||||
func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r *Registry, args map[string]interface{}, cache *Cache, opts ...InstallOption) Installer {
|
||||
i := Installer{
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
cli: cli,
|
||||
@@ -1082,14 +1083,21 @@ func NewAddonInstaller(ctx context.Context, cli client.Client, discoveryClient *
|
||||
cache: cache,
|
||||
dc: discoveryClient,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (h *Installer) enableAddon(addon *InstallPackage) error {
|
||||
var err error
|
||||
h.addon = addon
|
||||
err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
|
||||
if err != nil {
|
||||
return VersionUnMatchError{addonName: addon.Name, err: err}
|
||||
|
||||
if !h.skipVersionValidate {
|
||||
err = checkAddonVersionMeetRequired(h.ctx, addon.SystemRequirements, h.cli, h.dc)
|
||||
if err != nil {
|
||||
return VersionUnMatchError{addonName: addon.Name, err: err}
|
||||
}
|
||||
}
|
||||
|
||||
if err = h.installDependency(addon); err != nil {
|
||||
@@ -1445,10 +1453,23 @@ func checkSemVer(actual string, require string) (bool, error) {
|
||||
}
|
||||
|
||||
func fetchVelaCoreImageTag(ctx context.Context, k8sClient client.Client) (string, error) {
|
||||
deploy := &appsv1.Deployment{}
|
||||
if err := k8sClient.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: types.KubeVelaControllerDeployment}, deploy); err != nil {
|
||||
deployList := &appsv1.DeploymentList{}
|
||||
if err := k8sClient.List(ctx, deployList, client.MatchingLabels{oam.LabelControllerName: oam.ApplicationControllerName}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
deploy := appsv1.Deployment{}
|
||||
if len(deployList.Items) == 0 {
|
||||
// backward compatible logic old version which vela-core controller has no this label
|
||||
if err := k8sClient.Get(ctx, types2.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: types.KubeVelaControllerDeployment}, &deploy); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return "", errors.New("can't find a running KubeVela instance, please install it first")
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
deploy = deployList.Items[0]
|
||||
}
|
||||
|
||||
var tag string
|
||||
for _, c := range deploy.Spec.Template.Spec.Containers {
|
||||
if c.Name == types.DefaultKubeVelaReleaseName {
|
||||
|
||||
@@ -196,7 +196,7 @@ var _ = Describe("Addon func test", func() {
|
||||
It("fetchVelaCoreImageTag func test", func() {
|
||||
deploy = appsv1.Deployment{}
|
||||
tag, err := fetchVelaCoreImageTag(ctx, k8sClient)
|
||||
Expect(err).Should(util.NotFoundMatcher{})
|
||||
Expect(err).ShouldNot(BeNil())
|
||||
Expect(tag).Should(BeEquivalentTo(""))
|
||||
|
||||
Expect(yaml.Unmarshal([]byte(deployYaml), &deploy)).Should(BeNil())
|
||||
@@ -217,7 +217,7 @@ var _ = Describe("Addon func test", func() {
|
||||
|
||||
It("checkAddonVersionMeetRequired func test", func() {
|
||||
deploy = appsv1.Deployment{}
|
||||
Expect(checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=v1.2.1"}, k8sClient, dc)).Should(util.NotFoundMatcher{})
|
||||
Expect(checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=v1.2.1"}, k8sClient, dc)).ShouldNot(BeNil())
|
||||
Expect(yaml.Unmarshal([]byte(deployYaml), &deploy)).Should(BeNil())
|
||||
deploy.SetNamespace(types.DefaultKubeVelaNS)
|
||||
Expect(k8sClient.Create(ctx, &deploy)).Should(BeNil())
|
||||
@@ -408,6 +408,8 @@ kind: Deployment
|
||||
metadata:
|
||||
name: kubevela-vela-core
|
||||
namespace: vela-system
|
||||
labels:
|
||||
controller.oam.dev/name: vela-core
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-github/v32/github"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -47,6 +48,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
version2 "github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
@@ -791,6 +793,33 @@ func TestCheckAddonVersionMeetRequired(t *testing.T) {
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
return nil
|
||||
}),
|
||||
MockList: test.NewMockListFn(nil, func(obj client.ObjectList) error {
|
||||
robj := obj.(*appsv1.DeploymentList)
|
||||
list := &appsv1.DeploymentList{
|
||||
Items: []appsv1.Deployment{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
oam.LabelControllerName: oam.ApplicationControllerName,
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Image: "vela-core:v1.2.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
list.DeepCopyInto(robj)
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
ctx := context.Background()
|
||||
assert.NoError(t, checkAddonVersionMeetRequired(ctx, &SystemRequirements{VelaVersion: ">=1.2.4"}, k8sClient, nil))
|
||||
|
||||
BIN
pkg/addon/example-1.0.1.tgz
Normal file
BIN
pkg/addon/example-1.0.1.tgz
Normal file
Binary file not shown.
@@ -54,8 +54,8 @@ const (
|
||||
)
|
||||
|
||||
// EnableAddon will enable addon with dependency check, source is where addon from.
|
||||
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache) error {
|
||||
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache)
|
||||
func EnableAddon(ctx context.Context, name string, version string, cli client.Client, discoveryClient *discovery.DiscoveryClient, apply apply.Applicator, config *rest.Config, r Registry, args map[string]interface{}, cache *Cache, opts ...InstallOption) error {
|
||||
h := NewAddonInstaller(ctx, cli, discoveryClient, apply, config, &r, args, cache, opts...)
|
||||
pkg, err := h.loadInstallPackage(name, version)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -93,7 +93,7 @@ func DisableAddon(ctx context.Context, cli client.Client, name string, config *r
|
||||
}
|
||||
|
||||
// EnableAddonByLocalDir enable an addon from local dir
|
||||
func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}) error {
|
||||
func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli client.Client, dc *discovery.DiscoveryClient, applicator apply.Applicator, config *rest.Config, args map[string]interface{}, opts ...InstallOption) error {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -112,7 +112,7 @@ func EnableAddonByLocalDir(ctx context.Context, name string, dir string, cli cli
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil)
|
||||
h := NewAddonInstaller(ctx, cli, dc, applicator, config, &Registry{Name: LocalAddonRegistryName}, args, nil, opts...)
|
||||
needEnableAddonNames, err := h.checkDependency(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -86,7 +86,6 @@ var _ = Describe("test FindWholeAddonPackagesFromRegistry", func() {
|
||||
Expect(res).To(HaveLen(1))
|
||||
Expect(res[0].Name).To(Equal("velaux"))
|
||||
Expect(res[0].InstallPackage).ToNot(BeNil())
|
||||
Expect(res[0].APISchema).ToNot(BeNil())
|
||||
})
|
||||
It("should return one valid result, matching one registry", func() {
|
||||
res, err := FindWholeAddonPackagesFromRegistry(context.Background(), k8sClient, []string{"velaux"}, []string{"KubeVela"})
|
||||
@@ -94,7 +93,6 @@ var _ = Describe("test FindWholeAddonPackagesFromRegistry", func() {
|
||||
Expect(res).To(HaveLen(1))
|
||||
Expect(res[0].Name).To(Equal("velaux"))
|
||||
Expect(res[0].InstallPackage).ToNot(BeNil())
|
||||
Expect(res[0].APISchema).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -113,10 +111,8 @@ var _ = Describe("test FindWholeAddonPackagesFromRegistry", func() {
|
||||
Expect(res).To(HaveLen(2))
|
||||
Expect(res[0].Name).To(Equal("velaux"))
|
||||
Expect(res[0].InstallPackage).ToNot(BeNil())
|
||||
Expect(res[0].APISchema).ToNot(BeNil())
|
||||
Expect(res[1].Name).To(Equal("traefik"))
|
||||
Expect(res[1].InstallPackage).ToNot(BeNil())
|
||||
Expect(res[1].APISchema).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -127,7 +123,6 @@ var _ = Describe("test FindWholeAddonPackagesFromRegistry", func() {
|
||||
Expect(res).To(HaveLen(1))
|
||||
Expect(res[0].Name).To(Equal("velaux"))
|
||||
Expect(res[0].InstallPackage).ToNot(BeNil())
|
||||
Expect(res[0].APISchema).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -228,3 +228,11 @@ func usingAppsInfo(apps []v1beta1.Application) string {
|
||||
func IsVersionRegistry(r Registry) bool {
|
||||
return r.Helm != nil
|
||||
}
|
||||
|
||||
// InstallOption define additional option for installation
|
||||
type InstallOption func(installer *Installer)
|
||||
|
||||
// SkipValidateVersion means skip validating system version
|
||||
func SkipValidateVersion(installer *Installer) {
|
||||
installer.skipVersionValidate = true
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
// SystemInfo systemInfo model
|
||||
type SystemInfo struct {
|
||||
BaseModel
|
||||
SignedKey string `json:"signedKey"`
|
||||
InstallID string `json:"installID"`
|
||||
EnableCollection bool `json:"enableCollection"`
|
||||
LoginType string `json:"loginType"`
|
||||
|
||||
@@ -29,6 +29,12 @@ func init() {
|
||||
RegisterModel(&WorkflowRecord{})
|
||||
}
|
||||
|
||||
// Finished means the workflow record is finished
|
||||
const Finished = "true"
|
||||
|
||||
// UnFinished means the workflow record is not finished
|
||||
const UnFinished = "false"
|
||||
|
||||
// Workflow application delivery database model
|
||||
type Workflow struct {
|
||||
BaseModel
|
||||
|
||||
@@ -507,14 +507,14 @@ func (c *applicationServiceImpl) UpdateApplication(ctx context.Context, app *mod
|
||||
func (c *applicationServiceImpl) ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error) {
|
||||
var record = model.WorkflowRecord{
|
||||
AppPrimaryKey: appName,
|
||||
Finished: "false",
|
||||
Finished: model.UnFinished,
|
||||
}
|
||||
records, err := c.Store.List(ctx, &record, &datastore.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
record.Finished = "true"
|
||||
record.Finished = model.Finished
|
||||
records, err = c.Store.List(ctx, &record, &datastore.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
|
||||
@@ -57,7 +57,8 @@ const (
|
||||
GrantTypeRefresh = "refresh"
|
||||
)
|
||||
|
||||
var signedKey = ""
|
||||
// signedKey is the signed key of JWT
|
||||
var signedKey string
|
||||
|
||||
// AuthenticationService is the service of authentication
|
||||
type AuthenticationService interface {
|
||||
|
||||
@@ -60,7 +60,10 @@ var _ = Describe("Test cluster service function", func() {
|
||||
secret.Name = name
|
||||
secret.Namespace = prismclusterv1alpha1.StorageNamespace
|
||||
secret.SetAnnotations(map[string]string{prismclusterv1alpha1.AnnotationClusterAlias: alias})
|
||||
secret.SetLabels(map[string]string{clustergatewaycommon.LabelKeyClusterCredentialType: string(clustergatewayv1alpha1.CredentialTypeX509Certificate)})
|
||||
secret.SetLabels(map[string]string{
|
||||
clustergatewaycommon.LabelKeyClusterEndpointType: string(clustergatewayv1alpha1.ClusterEndpointTypeConst),
|
||||
clustergatewaycommon.LabelKeyClusterCredentialType: string(clustergatewayv1alpha1.CredentialTypeX509Certificate),
|
||||
})
|
||||
time.Sleep(time.Second)
|
||||
return k8sClient.Create(ctx, secret)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ type DefinitionService interface {
|
||||
UpdateDefinitionStatus(ctx context.Context, name string, status apisv1.UpdateDefinitionStatusRequest) (*apisv1.DetailDefinitionResponse, error)
|
||||
}
|
||||
|
||||
// DefinitionHidden means the definition can not be used in VelaUX
|
||||
const DefinitionHidden = "true"
|
||||
|
||||
type definitionServiceImpl struct {
|
||||
KubeClient client.Client `inject:"kubeClient"`
|
||||
}
|
||||
@@ -344,7 +347,7 @@ func (d *definitionServiceImpl) UpdateDefinitionStatus(ctx context.Context, name
|
||||
}
|
||||
if !exist && update.HiddenInUI {
|
||||
labels := def.GetLabels()
|
||||
labels[types.LabelDefinitionHidden] = "true"
|
||||
labels[types.LabelDefinitionHidden] = DefinitionHidden
|
||||
def.SetLabels(labels)
|
||||
if err := d.KubeClient.Update(ctx, def); err != nil {
|
||||
return nil, err
|
||||
|
||||
228
pkg/apiserver/domain/service/image.go
Normal file
228
pkg/apiserver/domain/service/image.go
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
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 service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
)
|
||||
|
||||
// NewImageService create a image service instance
|
||||
func NewImageService() ImageService {
|
||||
return &imageImpl{}
|
||||
}
|
||||
|
||||
// ImageService the image service provide some handler functions about the docker image
|
||||
type ImageService interface {
|
||||
ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error)
|
||||
GetImageInfo(ctx context.Context, project, secretName, imageName string) v1.ImageInfo
|
||||
}
|
||||
|
||||
type imageImpl struct {
|
||||
K8sClient client.Client `inject:"kubeClient"`
|
||||
}
|
||||
|
||||
// ListImageRepos list the image repositories via user configuration
|
||||
func (i *imageImpl) ListImageRepos(ctx context.Context, project string) ([]v1.ImageRegistry, error) {
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var repos []v1.ImageRegistry
|
||||
for _, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
repos = append(repos, v1.ImageRegistry{
|
||||
Name: secret.Name,
|
||||
SecretName: secret.Name,
|
||||
Domain: secret.Labels[types.LabelConfigIdentifier],
|
||||
})
|
||||
}
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetImageInfo get the image info from image registry
|
||||
func (i *imageImpl) GetImageInfo(ctx context.Context, project, secretName, imageName string) v1.ImageInfo {
|
||||
var imageInfo = v1.ImageInfo{
|
||||
Name: imageName,
|
||||
}
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
imageInfo.Message = "The image name is invalid"
|
||||
return imageInfo
|
||||
}
|
||||
registryDomain := ref.Context().RegistryStr()
|
||||
imageInfo.Registry = registryDomain
|
||||
var secrets corev1.SecretList
|
||||
if err := i.K8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
types.LabelConfigIdentifier: registryDomain,
|
||||
}); err != nil {
|
||||
log.Logger.Warnf("fail to list the docker registries, %s", err.Error())
|
||||
}
|
||||
var selectSecret []*corev1.Secret
|
||||
var selectSecretNames []string
|
||||
// get info with specified secret
|
||||
if secretName != "" {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secretName == secret.Name {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get info with the secret which match the registry domain
|
||||
if selectSecret == nil {
|
||||
for i, secret := range secrets.Items {
|
||||
if secret.Labels[types.LabelConfigProject] == "" || secret.Labels[types.LabelConfigProject] == project {
|
||||
if secret.Labels[types.LabelConfigIdentifier] == registryDomain {
|
||||
selectSecret = append(selectSecret, &secrets.Items[i])
|
||||
selectSecretNames = append(selectSecretNames, secret.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var username, password string
|
||||
var insecure = false
|
||||
var useHTTP = false
|
||||
imageInfo.SecretNames = selectSecretNames
|
||||
if len(selectSecret) > 0 {
|
||||
insecure, useHTTP, username, password = getAccountFromSecret(*selectSecret[0], registryDomain)
|
||||
}
|
||||
err = getImageInfo(imageName, insecure, useHTTP, username, password, &imageInfo)
|
||||
if err != nil {
|
||||
imageInfo.Message = fmt.Sprintf("Fail to get the image info:%s", err.Error())
|
||||
}
|
||||
return imageInfo
|
||||
}
|
||||
|
||||
// getAccountFromSecret get the username and password from the secret of `kubernetes.io/dockerconfigjson` type
|
||||
// refer: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||
func getAccountFromSecret(secret corev1.Secret, registryDomain string) (insecure, useHTTP bool, username, password string) {
|
||||
if secret.Data != nil {
|
||||
// If users use the self-signed certificate, enable the insecure-skip-verify
|
||||
insecure = string(secret.Data["insecure-skip-verify"]) == "true"
|
||||
useHTTP = string(secret.Data["protocol-use-http"]) == "true"
|
||||
conf := secret.Data[".dockerconfigjson"]
|
||||
if len(conf) > 0 {
|
||||
var authConfig map[string]map[string]map[string]string
|
||||
if err := json.Unmarshal(conf, &authConfig); err != nil {
|
||||
log.Logger.Warnf("fail to unmarshal the secret %s , %s", secret.Name, err.Error())
|
||||
return
|
||||
}
|
||||
if authConfig != nil && authConfig["auths"] != nil && authConfig["auths"][registryDomain] != nil {
|
||||
data := authConfig["auths"][registryDomain]
|
||||
username = data["username"]
|
||||
password = data["password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getImageInfo(imageName string, insecure, useHTTP bool, username, password string, info *v1.ImageInfo) error {
|
||||
var options []remote.Option
|
||||
if username != "" || password != "" {
|
||||
basic := &authn.Basic{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
options = append(options, remote.WithAuth(basic))
|
||||
}
|
||||
if insecure {
|
||||
options = append(options, remote.WithTransport(&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
// By default we wrap the transport in retries, so reduce the
|
||||
// default dial timeout to 5s to avoid 5x 30s of connection
|
||||
// timeouts when doing the "ping" on certain http registries.
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
// #nosec G402
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
|
||||
}))
|
||||
}
|
||||
|
||||
var parseOptions []name.Option
|
||||
if useHTTP {
|
||||
parseOptions = append(parseOptions, name.Insecure)
|
||||
}
|
||||
var err error
|
||||
ref, err := name.ParseReference(imageName, parseOptions...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image, err := remote.Image(ref, options...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "incorrect username or password") {
|
||||
return fmt.Errorf("incorrect username or password")
|
||||
}
|
||||
var terr *transport.Error
|
||||
if errors.As(err, &terr) {
|
||||
fmt.Println(terr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
info.Manifest, err = image.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get the manifest:%w", err)
|
||||
}
|
||||
info.Info, err = image.ConfigFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to get the config:%w", err)
|
||||
}
|
||||
for _, l := range info.Manifest.Layers {
|
||||
info.Size += l.Size
|
||||
}
|
||||
info.Size += info.Manifest.Config.Size
|
||||
return nil
|
||||
}
|
||||
67
pkg/apiserver/domain/service/image_test.go
Normal file
67
pkg/apiserver/domain/service/image_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
v1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
|
||||
)
|
||||
|
||||
func TestGetImageInfo(t *testing.T) {
|
||||
|
||||
s2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "index.docker.io",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"insecure-skip-verify": []byte("true"),
|
||||
".dockerconfigjson": []byte(`{"auths":{"index.docker.io":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
|
||||
insecure, useHTTP, user, pass := getAccountFromSecret(*s2, "index.docker.io")
|
||||
assert.DeepEqual(t, user, "xxx")
|
||||
assert.DeepEqual(t, pass, "yyy")
|
||||
assert.DeepEqual(t, insecure, true)
|
||||
assert.DeepEqual(t, useHTTP, false)
|
||||
|
||||
var cf v1.ImageInfo
|
||||
// Test the public image
|
||||
err := getImageInfo("nginx", false, false, "", "", &cf)
|
||||
assert.DeepEqual(t, err, nil)
|
||||
assert.DeepEqual(t, cf.Info.Config.Entrypoint, []string{"/docker-entrypoint.sh"})
|
||||
|
||||
// Test the private image
|
||||
err = getImageInfo("nginx424ru823-should-not-existed", false, false, "abc", "efg", &cf)
|
||||
assert.DeepEqual(t, err.Error(), "incorrect username or password")
|
||||
|
||||
err = getImageInfo("text.registry/test-image", false, false, "", "", &cf)
|
||||
assert.DeepEqual(t, err != nil, true)
|
||||
}
|
||||
@@ -18,15 +18,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -41,7 +38,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
image "github.com/oam-dev/kubevela/pkg/utils/imageregistry"
|
||||
)
|
||||
|
||||
// ProjectService project manage service.
|
||||
@@ -59,7 +55,6 @@ type ProjectService interface {
|
||||
UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error)
|
||||
Init(ctx context.Context) error
|
||||
GetConfigs(ctx context.Context, projectName, configType string) ([]*apisv1.Config, error)
|
||||
ValidateImage(ctx context.Context, projectName, image string) (*apisv1.ImageResponse, error)
|
||||
}
|
||||
|
||||
type projectServiceImpl struct {
|
||||
@@ -623,57 +618,3 @@ func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv
|
||||
Description: a.Annotations[types.AnnotationConfigDescription],
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projectServiceImpl) ValidateImage(ctx context.Context, projectName, image string) (*apisv1.ImageResponse, error) {
|
||||
return validateImage(ctx, p.K8sClient, projectName, image)
|
||||
}
|
||||
|
||||
func validateImage(ctx context.Context, k8sClient client.Client, project, imageName string) (*apisv1.ImageResponse, error) {
|
||||
var (
|
||||
secrets v1.SecretList
|
||||
username string
|
||||
password string
|
||||
imagePullSecret string
|
||||
)
|
||||
|
||||
ref, err := name.ParseReference(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageURL := ref.Context().RegistryStr()
|
||||
|
||||
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
|
||||
client.MatchingLabels{
|
||||
types.LabelConfigCatalog: types.VelaCoreConfig,
|
||||
types.LabelConfigType: types.ImageRegistry,
|
||||
types.LabelConfigIdentifier: ref.Context().RegistryStr(),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range secrets.Items {
|
||||
if s.Labels[types.LabelConfigProject] == "" || s.Labels[types.LabelConfigProject] == project {
|
||||
conf := s.Data[".dockerconfigjson"]
|
||||
var auths map[string]map[string]map[string]string
|
||||
if err := json.Unmarshal(conf, &auths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imagePullSecret = s.Name
|
||||
if auths["auths"] != nil && auths["auths"][imageURL] != nil {
|
||||
data := auths["auths"][imageURL]
|
||||
username = data["username"]
|
||||
password = data["password"]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
existed, err := image.IsExisted(username, password, imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apisv1.ImageResponse{
|
||||
Existed: existed,
|
||||
Secret: imagePullSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -18,20 +18,14 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
terraformtypes "github.com/oam-dev/terraform-controller/api/types"
|
||||
terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"gotest.tools/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -299,331 +293,3 @@ var _ = Describe("Test project service functions", func() {
|
||||
Expect(roles.Total).Should(BeEquivalentTo(0))
|
||||
})
|
||||
})
|
||||
|
||||
func TestProjectGetConfigs(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
|
||||
createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022")
|
||||
|
||||
app1 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a1",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
"config.oam.dev/project": "p1",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
app2 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "terraform-provider",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
app3 := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "a3",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
model.LabelSourceOfTruth: model.FromInner,
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: "dex-connector",
|
||||
"config.oam.dev/project": "p3",
|
||||
},
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: common.AppStatus{Phase: common.ApplicationRunning},
|
||||
}
|
||||
|
||||
provider1 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider1",
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(createdTime),
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsReady,
|
||||
},
|
||||
}
|
||||
|
||||
provider2 := &terraformapi.Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
},
|
||||
},
|
||||
Status: terraformapi.ProviderStatus{
|
||||
State: terraformtypes.ProviderIsNotReady,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(app1, app2, app3, provider1, provider2).Build()
|
||||
|
||||
h := &projectServiceImpl{K8sClient: k8sClient}
|
||||
|
||||
type args struct {
|
||||
projectName string
|
||||
configType string
|
||||
h ProjectService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
configs []*apisv1.Config
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "project is matched",
|
||||
args: args{
|
||||
projectName: "p1",
|
||||
configType: "terraform-provider",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a1",
|
||||
Project: "p1",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project is not matched",
|
||||
args: args{
|
||||
projectName: "p999",
|
||||
configType: "terraform-provider",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is empty",
|
||||
args: args{
|
||||
projectName: "p3",
|
||||
configType: "",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "terraform-provider",
|
||||
Name: "a2",
|
||||
Project: "",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
ConfigType: "dex-connector",
|
||||
Name: "a3",
|
||||
Project: "p3",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}, {
|
||||
Name: "provider1",
|
||||
CreatedTime: &createdTime,
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is dex",
|
||||
args: args{
|
||||
projectName: "p3",
|
||||
configType: "config-dex-connector",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
configs: []*apisv1.Config{{
|
||||
ConfigType: "dex-connector",
|
||||
Name: "a3",
|
||||
Project: "p3",
|
||||
CreatedTime: &createdTime,
|
||||
ApplicationStatus: "running",
|
||||
Status: "Ready",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config type is invalid",
|
||||
args: args{
|
||||
configType: "xxx",
|
||||
h: h,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "unsupported config type",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.GetConfigs(ctx, tc.args.projectName, tc.args.configType)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.configs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImage(t *testing.T) {
|
||||
s := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(s)
|
||||
corev1.AddToScheme(s)
|
||||
terraformapi.AddToScheme(s)
|
||||
|
||||
s1 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s1",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "abce34289jwerojwerofaf77.com789",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"abce34289jwerojwerofaf77.com789":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
k8sClient1 := fake.NewClientBuilder().WithScheme(s).WithObjects(s1).Build()
|
||||
h1 := &projectServiceImpl{K8sClient: k8sClient1}
|
||||
|
||||
s2 := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "s2",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
Labels: map[string]string{
|
||||
velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig,
|
||||
velatypes.LabelConfigType: velatypes.ImageRegistry,
|
||||
velatypes.LabelConfigProject: "",
|
||||
velatypes.LabelConfigIdentifier: "index.docker.io",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
".dockerconfigjson": []byte(`{"auths":{"index.docker.io":{"auth":"aHlicmlkY2xvdWRAcHJvZC5YTEyMw==","username":"xxx","password":"yyy"}}}`),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient2 := fake.NewClientBuilder().WithScheme(s).WithObjects(s2).Build()
|
||||
h2 := &projectServiceImpl{K8sClient: k8sClient2}
|
||||
|
||||
type args struct {
|
||||
project string
|
||||
imageName string
|
||||
h ProjectService
|
||||
}
|
||||
|
||||
type want struct {
|
||||
resp *apisv1.ImageResponse
|
||||
errMsg string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "validate image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "nginx",
|
||||
h: h1,
|
||||
},
|
||||
want: want{
|
||||
resp: &apisv1.ImageResponse{
|
||||
Existed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "abce34289jwerojwerofaf77.com789/d/e:v1",
|
||||
h: h1,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "Get",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private docker image",
|
||||
args: args{
|
||||
project: "p1",
|
||||
imageName: "nginx424ru823-should-not-existed",
|
||||
h: h2,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "incorrect username or password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := tc.args.h.ValidateImage(ctx, tc.args.project, tc.args.imageName)
|
||||
if tc.want.errMsg != "" || err != nil {
|
||||
assert.ErrorContains(t, err, tc.want.errMsg)
|
||||
}
|
||||
assert.DeepEqual(t, got, tc.want.resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,6 @@ var ResourceMaps = map[string]resourceMetadata{
|
||||
},
|
||||
"applicationTemplate": {},
|
||||
"config": {},
|
||||
"image": {},
|
||||
},
|
||||
pathName: "projectName",
|
||||
},
|
||||
|
||||
@@ -50,7 +50,7 @@ func InitServiceBean(c config.Config) []interface{} {
|
||||
return []interface{}{
|
||||
clusterService, rbacService, projectService, envService, targetService, workflowService, oamApplicationService,
|
||||
velaQLService, definitionService, addonService, envBindingService, systemInfoService, helmService, userService,
|
||||
authenticationService, configService, applicationService, webhookService,
|
||||
authenticationService, configService, applicationService, webhookService, NewImageService(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ func (u systemInfoServiceImpl) Get(ctx context.Context) (*model.SystemInfo, erro
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
info.SignedKey = rand.String(32)
|
||||
installID := rand.String(16)
|
||||
info.InstallID = installID
|
||||
info.EnableCollection = true
|
||||
@@ -159,7 +160,7 @@ func (u systemInfoServiceImpl) Init(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedKey = info.InstallID
|
||||
signedKey = info.SignedKey
|
||||
_, err = initDexConfig(ctx, u.KubeClient, "http://velaux.com")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -79,5 +81,9 @@ func (v *velaQLServiceImpl) QueryView(ctx context.Context, velaQL string) (*apis
|
||||
log.Logger.Errorf("decode the velaQL response to json failure %s", err.Error())
|
||||
return nil, bcode.ErrParseQuery2Json
|
||||
}
|
||||
if strings.Contains(velaQL, "collect-logs") {
|
||||
enc, _ := base64.StdEncoding.DecodeString(resp["logs"].(string))
|
||||
resp["logs"] = string(enc)
|
||||
}
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
@@ -426,6 +427,11 @@ func (j *jfrogHandlerImpl) handle(ctx context.Context, webhookTrigger *model.App
|
||||
return nil, err
|
||||
}
|
||||
image := fmt.Sprintf("%s/%s:%s", jfrogReq.Data.RepoKey, jfrogReq.Data.ImageName, jfrogReq.Data.Tag)
|
||||
pathArray := strings.Split(jfrogReq.Data.Path, "/")
|
||||
if len(pathArray) > 2 {
|
||||
image = fmt.Sprintf("%s/%s:%s", jfrogReq.Data.RepoKey, strings.Join(pathArray[:len(pathArray)-2], "/"), jfrogReq.Data.Tag)
|
||||
}
|
||||
|
||||
if jfrogReq.Data.URL != "" {
|
||||
image = fmt.Sprintf("%s/%s", jfrogReq.Data.URL, image)
|
||||
}
|
||||
@@ -441,7 +447,7 @@ func (j *jfrogHandlerImpl) handle(ctx context.Context, webhookTrigger *model.App
|
||||
TriggerType: apisv1.TriggerTypeWebhook,
|
||||
Force: true,
|
||||
ImageInfo: &model.ImageInfo{
|
||||
Type: model.PayloadTypeHarbor,
|
||||
Type: model.PayloadTypeJFrog,
|
||||
Resource: &model.ImageResource{
|
||||
Digest: jfrogReq.Data.Digest,
|
||||
Tag: jfrogReq.Data.Tag,
|
||||
|
||||
@@ -322,7 +322,17 @@ func genClusterCountInfo(num int) string {
|
||||
return "<10"
|
||||
case num < 50:
|
||||
return "<50"
|
||||
case num < 100:
|
||||
return "<100"
|
||||
case num < 150:
|
||||
return "<150"
|
||||
case num < 200:
|
||||
return "<200"
|
||||
case num < 300:
|
||||
return "<300"
|
||||
case num < 500:
|
||||
return "<500"
|
||||
default:
|
||||
return ">=50"
|
||||
return ">=500"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +211,28 @@ func TestGenClusterCountInfo(t *testing.T) {
|
||||
res: "<50",
|
||||
},
|
||||
{
|
||||
count: 100,
|
||||
res: ">=50",
|
||||
count: 90,
|
||||
res: "<100",
|
||||
},
|
||||
{
|
||||
count: 137,
|
||||
res: "<150",
|
||||
},
|
||||
{
|
||||
count: 170,
|
||||
res: "<200",
|
||||
},
|
||||
{
|
||||
count: 270,
|
||||
res: "<300",
|
||||
},
|
||||
{
|
||||
count: 400,
|
||||
res: "<500",
|
||||
},
|
||||
{
|
||||
count: 520,
|
||||
res: ">=500",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
registryv1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
@@ -1380,3 +1381,26 @@ type ChartRepoResponse struct {
|
||||
type ChartRepoResponseList struct {
|
||||
ChartRepoResponse []*ChartRepoResponse `json:"repos"`
|
||||
}
|
||||
|
||||
// ImageInfo the docker image info
|
||||
type ImageInfo struct {
|
||||
Name string `json:"name"`
|
||||
SecretNames []string `json:"secretNames"`
|
||||
Registry string `json:"registry"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Info *registryv1.ConfigFile `json:"info,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
Manifest *registryv1.Manifest `json:"manifest"`
|
||||
}
|
||||
|
||||
// ImageRegistry the image repository info
|
||||
type ImageRegistry struct {
|
||||
Name string `json:"name"`
|
||||
SecretName string `json:"secretName"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
// ListImageRegistryResponse the response struct of listing the image registries
|
||||
type ListImageRegistryResponse struct {
|
||||
Registries []ImageRegistry `json:"registries"`
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func InitAPIBean() []interface{} {
|
||||
RegisterAPIInterface(NewTargetAPIInterface())
|
||||
RegisterAPIInterface(NewVelaQLAPIInterface())
|
||||
RegisterAPIInterface(NewWebhookAPIInterface())
|
||||
RegisterAPIInterface(NewHelmAPIInterface())
|
||||
RegisterAPIInterface(NewRepositoryAPIInterface())
|
||||
|
||||
// Authentication
|
||||
RegisterAPIInterface(NewAuthenticationAPIInterface())
|
||||
|
||||
@@ -203,16 +203,6 @@ func (n *projectAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.Config{}))
|
||||
|
||||
ws.Route(ws.GET("/{projectName}/validate_image").To(n.validateImage).
|
||||
Doc("validate an image in a project").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Filter(n.RbacService.CheckPerm("project/image", "get")).
|
||||
Param(ws.QueryParameter("image", "image name").DataType("string")).
|
||||
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
|
||||
Returns(200, "OK", []*apis.ImageResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]*apis.ImageResponse{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
@@ -599,23 +589,3 @@ func (n *projectAPIInterface) getConfigs(req *restful.Request, res *restful.Resp
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (n *projectAPIInterface) validateImage(req *restful.Request, res *restful.Response) {
|
||||
resp, err := n.ProjectService.ValidateImage(req.Request.Context(), req.PathParameter("projectName"), req.QueryParameter("image"))
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(map[string]interface{}{"data": resp})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
||||
@@ -29,16 +28,18 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
type helmAPIInterface struct {
|
||||
HelmService service.HelmService `inject:""`
|
||||
type repositoryAPIInterface struct {
|
||||
HelmService service.HelmService `inject:""`
|
||||
ImageService service.ImageService `inject:""`
|
||||
RbacService service.RBACService `inject:""`
|
||||
}
|
||||
|
||||
// NewHelmAPIInterface will return helm APIInterface
|
||||
func NewHelmAPIInterface() Interface {
|
||||
return &helmAPIInterface{}
|
||||
// NewRepositoryAPIInterface will return the repository APIInterface
|
||||
func NewRepositoryAPIInterface() Interface {
|
||||
return &repositoryAPIInterface{}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
func (h repositoryAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(versionPrefix+"/repository").
|
||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
||||
@@ -47,11 +48,12 @@ func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
|
||||
tags := []string{"repository", "helm"}
|
||||
|
||||
// List charts
|
||||
// List chart repos
|
||||
ws.Route(ws.GET("/chart_repos").To(h.listRepo).
|
||||
Doc("list chart repo").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string")).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", []string{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
@@ -86,11 +88,31 @@ func (h helmAPIInterface) GetWebServiceRoute() *restful.WebService {
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Route(ws.GET("/image/repos").To(h.getImageRepos).
|
||||
Doc("get the oci repos").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", v1.ListImageRegistryResponse{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Route(ws.GET("/image/info").To(h.getImageInfo).
|
||||
Doc("get the oci repos").
|
||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||
Param(ws.QueryParameter("project", "the config project").DataType("string").Required(true)).
|
||||
Param(ws.QueryParameter("name", "the image name").DataType("string").Required(true)).
|
||||
Param(ws.QueryParameter("secretName", "the secret name of the image repository").DataType("string")).
|
||||
Filter(h.RbacService.CheckPerm("project/config", "list")).
|
||||
Returns(200, "OK", v1.ImageInfo{}).
|
||||
Returns(400, "Bad Request", bcode.Bcode{}).
|
||||
Writes([]string{}))
|
||||
|
||||
ws.Filter(authCheckFilter)
|
||||
return ws
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listCharts(req *restful.Request, res *restful.Response) {
|
||||
url := utils.Sanitize(req.QueryParameter("repoUrl"))
|
||||
secName := utils.Sanitize(req.QueryParameter("secretName"))
|
||||
skipCache, err := isSkipCache(req)
|
||||
@@ -98,7 +120,7 @@ func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response
|
||||
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
|
||||
return
|
||||
}
|
||||
charts, err := h.HelmService.ListChartNames(context.Background(), url, secName, skipCache)
|
||||
charts, err := h.HelmService.ListChartNames(req.Request.Context(), url, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -110,7 +132,7 @@ func (h helmAPIInterface) listCharts(req *restful.Request, res *restful.Response
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listVersions(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
chartName := req.PathParameter("chart")
|
||||
secName := req.QueryParameter("secretName")
|
||||
@@ -120,7 +142,7 @@ func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Respon
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.HelmService.ListChartVersions(context.Background(), url, chartName, secName, skipCache)
|
||||
versions, err := h.HelmService.ListChartVersions(req.Request.Context(), url, chartName, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -132,7 +154,7 @@ func (h helmAPIInterface) listVersions(req *restful.Request, res *restful.Respon
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) chartValues(req *restful.Request, res *restful.Response) {
|
||||
url := req.QueryParameter("repoUrl")
|
||||
secName := req.QueryParameter("secretName")
|
||||
chartName := req.PathParameter("chart")
|
||||
@@ -143,7 +165,7 @@ func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Respons
|
||||
return
|
||||
}
|
||||
|
||||
versions, err := h.HelmService.GetChartValues(context.Background(), url, chartName, version, secName, skipCache)
|
||||
versions, err := h.HelmService.GetChartValues(req.Request.Context(), url, chartName, version, secName, skipCache)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -155,9 +177,9 @@ func (h helmAPIInterface) chartValues(req *restful.Request, res *restful.Respons
|
||||
}
|
||||
}
|
||||
|
||||
func (h helmAPIInterface) listRepo(req *restful.Request, res *restful.Response) {
|
||||
func (h repositoryAPIInterface) listRepo(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
repos, err := h.HelmService.ListChartRepo(context.Background(), project)
|
||||
repos, err := h.HelmService.ListChartRepo(req.Request.Context(), project)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
@@ -169,6 +191,31 @@ func (h helmAPIInterface) listRepo(req *restful.Request, res *restful.Response)
|
||||
}
|
||||
}
|
||||
|
||||
func (h repositoryAPIInterface) getImageRepos(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
repos, err := h.ImageService.ListImageRepos(req.Request.Context(), project)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
err = res.WriteEntity(v1.ListImageRegistryResponse{Registries: repos})
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (h repositoryAPIInterface) getImageInfo(req *restful.Request, res *restful.Response) {
|
||||
project := req.QueryParameter("project")
|
||||
imageInfo := h.ImageService.GetImageInfo(req.Request.Context(), project, req.QueryParameter("secretName"), req.QueryParameter("name"))
|
||||
err := res.WriteEntity(imageInfo)
|
||||
if err != nil {
|
||||
bcode.ReturnError(req, res, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isSkipCache(req *restful.Request) (bool, error) {
|
||||
skipStr := req.QueryParameter("skipCache")
|
||||
skipCache := false
|
||||
@@ -82,8 +82,8 @@ func (c Condition) Validate() error {
|
||||
if c.JSONKey == "" {
|
||||
return fmt.Errorf("the json key of the condition can not be empty")
|
||||
}
|
||||
if c.Action != "enable" && c.Action != "disable" {
|
||||
return fmt.Errorf("the action of the condition must be enable or disable")
|
||||
if c.Action != "enable" && c.Action != "disable" && c.Action != "" {
|
||||
return fmt.Errorf("the action of the condition only supports enable, disable or leave it empty")
|
||||
}
|
||||
if c.Op != "" && !StringsContain([]string{"==", "!=", "in"}, c.Op) {
|
||||
return fmt.Errorf("the op of the condition must be `==` 、`!=` and `in`")
|
||||
|
||||
@@ -97,21 +97,21 @@ func (wl *Workload) EvalContext(ctx process.Context) error {
|
||||
}
|
||||
|
||||
// EvalStatus eval workload status
|
||||
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (string, error) {
|
||||
// if the standard workload is managed by trait always return empty message
|
||||
if wl.SkipApplyWorkload {
|
||||
return "", nil
|
||||
}
|
||||
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus, wl.Params)
|
||||
return wl.engine.Status(ctx, cli, accessor, wl.FullTemplate.CustomStatus, wl.Params)
|
||||
}
|
||||
|
||||
// EvalHealth eval workload health check
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
func (wl *Workload) EvalHealth(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (bool, error) {
|
||||
// if health of template is not set or standard workload is managed by trait always return true
|
||||
if wl.FullTemplate.Health == "" || wl.SkipApplyWorkload {
|
||||
return true, nil
|
||||
}
|
||||
return wl.engine.HealthCheck(ctx, client, namespace, wl.FullTemplate.Health)
|
||||
return wl.engine.HealthCheck(ctx, client, accessor, wl.FullTemplate.Health)
|
||||
}
|
||||
|
||||
// Scope defines the scope of workload
|
||||
@@ -145,16 +145,16 @@ func (trait *Trait) EvalContext(ctx process.Context) error {
|
||||
}
|
||||
|
||||
// EvalStatus eval trait status
|
||||
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
|
||||
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat, trait.Params)
|
||||
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor) (string, error) {
|
||||
return trait.engine.Status(ctx, cli, accessor, trait.CustomStatusFormat, trait.Params)
|
||||
}
|
||||
|
||||
// EvalHealth eval trait health check
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, namespace string) (bool, error) {
|
||||
func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, accessor util.NamespaceAccessor) (bool, error) {
|
||||
if trait.FullTemplate.Health == "" {
|
||||
return true, nil
|
||||
}
|
||||
return trait.engine.HealthCheck(ctx, client, namespace, trait.HealthCheckPolicy)
|
||||
return trait.engine.HealthCheck(ctx, client, accessor, trait.HealthCheckPolicy)
|
||||
}
|
||||
|
||||
// Appfile describes application
|
||||
|
||||
@@ -365,6 +365,9 @@ func (p *Parser) parsePoliciesFromRevision(ctx context.Context, af *Appfile) (er
|
||||
return err
|
||||
}
|
||||
for _, policy := range af.Policies {
|
||||
if policy.Properties == nil && policy.Type != v1alpha1.DebugPolicyType {
|
||||
return fmt.Errorf("policy %s named %s must not have empty properties", policy.Type, policy.Name)
|
||||
}
|
||||
switch policy.Type {
|
||||
case v1alpha1.GarbageCollectPolicyType:
|
||||
case v1alpha1.ApplyOncePolicyType:
|
||||
@@ -390,6 +393,9 @@ func (p *Parser) parsePolicies(ctx context.Context, af *Appfile) (err error) {
|
||||
return err
|
||||
}
|
||||
for _, policy := range af.Policies {
|
||||
if policy.Properties == nil && policy.Type != v1alpha1.DebugPolicyType {
|
||||
return fmt.Errorf("policy %s named %s must not have empty properties", policy.Type, policy.Name)
|
||||
}
|
||||
switch policy.Type {
|
||||
case v1alpha1.GarbageCollectPolicyType:
|
||||
case v1alpha1.ApplyOncePolicyType:
|
||||
|
||||
@@ -243,6 +243,20 @@ spec:
|
||||
image: "busybox"
|
||||
`
|
||||
|
||||
const appfileYamlEmptyPolicy = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: application-sample
|
||||
namespace: default
|
||||
spec:
|
||||
components: []
|
||||
policies:
|
||||
- type: garbage-collect
|
||||
name: somename
|
||||
properties:
|
||||
`
|
||||
|
||||
var _ = Describe("Test application parser", func() {
|
||||
It("Test we can parse an application to an appFile", func() {
|
||||
o := v1beta1.Application{}
|
||||
@@ -282,6 +296,14 @@ var _ = Describe("Test application parser", func() {
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = NewApplicationParser(&tclient, dm, pd).GenerateAppFile(context.TODO(), ¬found)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
By("app with empty policy")
|
||||
emptyPolicy := v1beta1.Application{}
|
||||
err = yaml.Unmarshal([]byte(appfileYamlEmptyPolicy), &emptyPolicy)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = NewApplicationParser(&tclient, dm, pd).GenerateAppFile(context.TODO(), &emptyPolicy)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("have empty properties"))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ func ContextWithUserInfo(ctx context.Context, app *v1beta1.Application) context.
|
||||
return request.WithUser(ctx, GetUserInfoInAnnotation(&app.ObjectMeta))
|
||||
}
|
||||
|
||||
// ContextClearUserInfo clear user info in context
|
||||
func ContextClearUserInfo(ctx context.Context) context.Context {
|
||||
return request.WithUser(ctx, nil)
|
||||
}
|
||||
|
||||
// SetUserInfoInAnnotation set username and group from userInfo into annotations
|
||||
// it will clear the existing service account annotation in avoid of permission leak
|
||||
func SetUserInfoInAnnotation(obj *metav1.ObjectMeta, userInfo authv1.UserInfo) {
|
||||
|
||||
@@ -51,7 +51,7 @@ func (c *HTTPCmd) Run(meta *registry.Meta) (res interface{}, err error) {
|
||||
var (
|
||||
r io.Reader
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{},
|
||||
Transport: http.DefaultTransport,
|
||||
Timeout: time.Second * 3,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -86,4 +86,7 @@ type Args struct {
|
||||
|
||||
// IgnoreAppWithoutControllerRequirement indicates that application controller will not process the app without 'app.oam.dev/controller-version-require' annotation.
|
||||
IgnoreAppWithoutControllerRequirement bool
|
||||
|
||||
// IgnoreDefinitionWithoutControllerRequirement indicates that trait/component/workflowstep definition controller will not process the definition without 'definition.oam.dev/controller-version-require' annotation.
|
||||
IgnoreDefinitionWithoutControllerRequirement bool
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
if annotations := app.GetAnnotations(); annotations == nil || annotations[oam.AnnotationKubeVelaVersion] == "" {
|
||||
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationKubeVelaVersion, version.VelaVersion)
|
||||
}
|
||||
logCtx.AddTag("publish_version", app.GetAnnotations()[oam.AnnotationKubeVelaVersion])
|
||||
logCtx.AddTag("publish_version", app.GetAnnotations()[oam.AnnotationPublishVersion])
|
||||
|
||||
appParser := appfile.NewApplicationParser(r.Client, r.dm, r.pd)
|
||||
handler, err := NewAppHandler(logCtx, r, app, appParser)
|
||||
@@ -307,6 +307,11 @@ func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *
|
||||
}))
|
||||
defer subCtx.Commit("finish gc resourceTrackers")
|
||||
|
||||
statusUpdater := r.updateStatus
|
||||
if isPatch {
|
||||
statusUpdater = r.patchStatus
|
||||
}
|
||||
|
||||
var options []resourcekeeper.GCOption
|
||||
if !gcOutdated {
|
||||
options = append(options, resourcekeeper.DisableMarkStageGCOption{}, resourcekeeper.DisableGCComponentRevisionOption{}, resourcekeeper.DisableLegacyGCOption{})
|
||||
@@ -314,8 +319,10 @@ func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *
|
||||
finished, waiting, err := handler.resourceKeeper.GarbageCollect(logCtx, options...)
|
||||
if err != nil {
|
||||
logCtx.Error(err, "Failed to gc resourcetrackers")
|
||||
r.Recorder.Event(handler.app, event.Warning(velatypes.ReasonFailedGC, err))
|
||||
return r.endWithNegativeCondition(logCtx, handler.app, condition.ReconcileError(err), phase)
|
||||
cond := condition.Deleting()
|
||||
cond.Message = fmt.Sprintf("error encountered during garbage collection: %s", err.Error())
|
||||
handler.app.Status.SetConditions(cond)
|
||||
return r.result(statusUpdater(logCtx, handler.app, phase)).ret()
|
||||
}
|
||||
if !finished {
|
||||
logCtx.Info("GarbageCollecting resourcetrackers unfinished")
|
||||
@@ -324,13 +331,13 @@ func (r *Reconciler) gcResourceTrackers(logCtx monitorContext.Context, handler *
|
||||
cond.Message = fmt.Sprintf("Waiting for %s to delete. (At least %d resources are deleting.)", waiting[0].DisplayName(), len(waiting))
|
||||
}
|
||||
handler.app.Status.SetConditions(cond)
|
||||
return r.result(r.patchStatus(logCtx, handler.app, phase)).requeue(baseGCBackoffWaitTime).ret()
|
||||
return r.result(statusUpdater(logCtx, handler.app, phase)).requeue(baseGCBackoffWaitTime).ret()
|
||||
}
|
||||
logCtx.Info("GarbageCollected resourcetrackers")
|
||||
if !isPatch {
|
||||
return r.result(r.updateStatus(logCtx, handler.app, common.ApplicationRunningWorkflow)).ret()
|
||||
phase = common.ApplicationRunningWorkflow
|
||||
}
|
||||
return r.result(r.patchStatus(logCtx, handler.app, phase)).ret()
|
||||
return r.result(statusUpdater(logCtx, handler.app, phase)).ret()
|
||||
}
|
||||
|
||||
type reconcileResult struct {
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/monitor/metrics"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
)
|
||||
|
||||
@@ -215,17 +216,14 @@ func (h *AppHandler) ProduceArtifacts(ctx context.Context, comps []*types.Compon
|
||||
|
||||
// nolint
|
||||
func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Workload, appRev *v1beta1.ApplicationRevision, overrideNamespace string) (*common.ApplicationComponentStatus, bool, error) {
|
||||
namespace := h.app.Namespace
|
||||
if overrideNamespace != "" {
|
||||
namespace = overrideNamespace
|
||||
}
|
||||
accessor := util.NewApplicationResourceNamespaceAccessor(h.app.Namespace, overrideNamespace)
|
||||
|
||||
var (
|
||||
status = common.ApplicationComponentStatus{
|
||||
Name: wl.Name,
|
||||
WorkloadDefinition: wl.FullTemplate.Reference.Definition,
|
||||
Healthy: true,
|
||||
Namespace: namespace,
|
||||
Namespace: accessor.Namespace(),
|
||||
Cluster: multicluster.ClusterNameInContext(ctx),
|
||||
}
|
||||
appName = appRev.Spec.Application.Name
|
||||
@@ -235,10 +233,10 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
|
||||
if wl.CapabilityCategory == types.TerraformCategory {
|
||||
var configuration terraforv1beta2.Configuration
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: namespace}, &configuration); err != nil {
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: accessor.Namespace()}, &configuration); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
var legacyConfiguration terraforv1beta1.Configuration
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: namespace}, &legacyConfiguration); err != nil {
|
||||
if err := h.r.Client.Get(ctx, client.ObjectKey{Name: wl.Name, Namespace: accessor.Namespace()}, &legacyConfiguration); err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, check health error", appName, wl.Name)
|
||||
}
|
||||
isHealth = setStatus(&status, legacyConfiguration.Status.ObservedGeneration, legacyConfiguration.Generation,
|
||||
@@ -251,12 +249,12 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
appRev.Name, configuration.Status.Apply.State, configuration.Status.Apply.Message)
|
||||
}
|
||||
} else {
|
||||
if ok, err := wl.EvalHealth(wl.Ctx, h.r.Client, namespace); !ok || err != nil {
|
||||
if ok, err := wl.EvalHealth(wl.Ctx, h.r.Client, accessor); !ok || err != nil {
|
||||
isHealth = false
|
||||
status.Healthy = false
|
||||
}
|
||||
|
||||
status.Message, err = wl.EvalStatus(wl.Ctx, h.r.Client, namespace)
|
||||
status.Message, err = wl.EvalStatus(wl.Ctx, h.r.Client, accessor)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, evaluate workload status message error", appName, wl.Name)
|
||||
}
|
||||
@@ -264,19 +262,26 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
|
||||
var traitStatusList []common.ApplicationTraitStatus
|
||||
for _, tr := range wl.Traits {
|
||||
traitOverrideNamespace := overrideNamespace
|
||||
if tr.FullTemplate.TraitDefinition.Spec.ControlPlaneOnly {
|
||||
traitOverrideNamespace = appRev.GetNamespace()
|
||||
wl.Ctx.SetCtx(context.WithValue(wl.Ctx.GetCtx(), multicluster.ClusterContextKey, multicluster.ClusterLocalName))
|
||||
}
|
||||
_accessor := util.NewApplicationResourceNamespaceAccessor(h.app.Namespace, traitOverrideNamespace)
|
||||
var traitStatus = common.ApplicationTraitStatus{
|
||||
Type: tr.Name,
|
||||
Healthy: true,
|
||||
}
|
||||
if ok, err := tr.EvalHealth(wl.Ctx, h.r.Client, namespace); !ok || err != nil {
|
||||
if ok, err := tr.EvalHealth(wl.Ctx, h.r.Client, _accessor); !ok || err != nil {
|
||||
isHealth = false
|
||||
traitStatus.Healthy = false
|
||||
}
|
||||
traitStatus.Message, err = tr.EvalStatus(wl.Ctx, h.r.Client, namespace)
|
||||
traitStatus.Message, err = tr.EvalStatus(wl.Ctx, h.r.Client, _accessor)
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appName, wl.Name, tr.Name)
|
||||
}
|
||||
traitStatusList = append(traitStatusList, traitStatus)
|
||||
wl.Ctx.SetCtx(context.WithValue(wl.Ctx.GetCtx(), multicluster.ClusterContextKey, status.Cluster))
|
||||
}
|
||||
|
||||
status.Traits = traitStatusList
|
||||
@@ -299,12 +304,12 @@ func setStatus(status *common.ApplicationComponentStatus, observedGeneration, ge
|
||||
}
|
||||
return true
|
||||
}
|
||||
status.Message = message
|
||||
if !isLatest() || state != terraformtypes.Available {
|
||||
status.Healthy = false
|
||||
return false
|
||||
}
|
||||
status.Healthy = true
|
||||
status.Message = message
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/velaql/providers/query"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/http"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers/kube"
|
||||
@@ -81,6 +82,7 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context,
|
||||
terraformProvider.Install(handlerProviders, app, func(comp common.ApplicationComponent) (*appfile.Workload, error) {
|
||||
return appParser.ParseWorkloadFromRevision(comp, appRev)
|
||||
})
|
||||
query.Install(handlerProviders, h.r.Client, nil)
|
||||
|
||||
var tasks []wfTypes.TaskRunner
|
||||
for _, step := range af.WorkflowSteps {
|
||||
@@ -188,7 +190,7 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
|
||||
}
|
||||
|
||||
func checkDependsOnValidComponent(dependsOnComponentNames, allComponentNames []string) (string, bool) {
|
||||
// does not depends on other components
|
||||
// does not depend on other components
|
||||
if dependsOnComponentNames == nil {
|
||||
return "", true
|
||||
}
|
||||
|
||||
@@ -967,7 +967,7 @@ func (h historiesByComponentRevision) Less(i, j int) bool {
|
||||
|
||||
// UpdateApplicationRevisionStatus update application revision status
|
||||
func (h *AppHandler) UpdateApplicationRevisionStatus(ctx context.Context, appRev *v1beta1.ApplicationRevision, succeed bool, wfStatus *common.WorkflowStatus) {
|
||||
if appRev == nil {
|
||||
if appRev == nil || DisableAllApplicationRevision {
|
||||
return
|
||||
}
|
||||
appRev.Status.Succeeded = succeed
|
||||
|
||||
@@ -43,17 +43,24 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// Reconciler reconciles a ComponentDefinition object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
}
|
||||
|
||||
type options struct {
|
||||
defRevLimit int
|
||||
concurrentReconciles int
|
||||
ignoreDefNoCtrlReq bool
|
||||
controllerVersion string
|
||||
}
|
||||
|
||||
// Reconcile is the main logic for ComponentDefinition controller
|
||||
@@ -68,6 +75,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
if !r.matchControllerRequirement(&componentDefinition) {
|
||||
klog.InfoS("skip componentDefinition: not match the controller requirement of componentDefinition", "componentDefinition", klog.KObj(&componentDefinition))
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// refresh package discover when componentDefinition is registered
|
||||
if componentDefinition.Spec.Workload.Type != types.AutoDetectWorkloadDefinition {
|
||||
err := utils.RefreshPackageDiscover(ctx, r.Client, r.dm, r.pd, &componentDefinition)
|
||||
@@ -187,12 +199,32 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// Setup adds a controller that reconciles ComponentDefinition.
|
||||
func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
}
|
||||
|
||||
func parseOptions(args oamctrl.Args) options {
|
||||
return options{
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
ignoreDefNoCtrlReq: args.IgnoreDefinitionWithoutControllerRequirement,
|
||||
controllerVersion: version.VelaVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reconciler) matchControllerRequirement(componentDefinition *v1beta1.ComponentDefinition) bool {
|
||||
if componentDefinition.Annotations != nil {
|
||||
if requireVersion, ok := componentDefinition.Annotations[oam.AnnotationControllerRequirement]; ok {
|
||||
return requireVersion == r.controllerVersion
|
||||
}
|
||||
}
|
||||
if r.ignoreDefNoCtrlReq {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -90,11 +90,13 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r = Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
defRevLimit: defRevisionLimit,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
var ctx context.Context
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
af "github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -478,7 +479,8 @@ func CUEBasedHealthCheck(ctx context.Context, c client.Client, wlRef WorkloadRef
|
||||
okToCheckTrait = true
|
||||
return
|
||||
}
|
||||
isHealthy, err := wl.EvalHealth(pCtx, c, ns)
|
||||
accessor := util.NewApplicationResourceNamespaceAccessor(ns, "")
|
||||
isHealthy, err := wl.EvalHealth(pCtx, c, accessor)
|
||||
if err != nil {
|
||||
wlHealth.HealthStatus = StatusUnhealthy
|
||||
wlHealth.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
|
||||
@@ -490,7 +492,7 @@ func CUEBasedHealthCheck(ctx context.Context, c client.Client, wlRef WorkloadRef
|
||||
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
|
||||
wlHealth.HealthStatus = StatusUnhealthy
|
||||
}
|
||||
wlHealth.CustomStatusMsg, err = wl.EvalStatus(pCtx, c, ns)
|
||||
wlHealth.CustomStatusMsg, err = wl.EvalStatus(pCtx, c, accessor)
|
||||
if err != nil {
|
||||
wlHealth.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
|
||||
}
|
||||
@@ -522,7 +524,8 @@ func CUEBasedHealthCheck(ctx context.Context, c client.Client, wlRef WorkloadRef
|
||||
traits[i] = tHealth
|
||||
continue
|
||||
}
|
||||
isHealthy, err := tr.EvalHealth(pCtx, c, ns)
|
||||
accessor := util.NewApplicationResourceNamespaceAccessor("", ns)
|
||||
isHealthy, err := tr.EvalHealth(pCtx, c, accessor)
|
||||
if err != nil {
|
||||
tHealth.HealthStatus = StatusUnhealthy
|
||||
tHealth.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
|
||||
@@ -535,7 +538,7 @@ func CUEBasedHealthCheck(ctx context.Context, c client.Client, wlRef WorkloadRef
|
||||
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
|
||||
tHealth.HealthStatus = StatusUnhealthy
|
||||
}
|
||||
tHealth.CustomStatusMsg, err = tr.EvalStatus(pCtx, c, ns)
|
||||
tHealth.CustomStatusMsg, err = tr.EvalStatus(pCtx, c, accessor)
|
||||
if err != nil {
|
||||
tHealth.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
|
||||
}
|
||||
|
||||
@@ -42,17 +42,24 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// Reconciler reconciles a TraitDefinition object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
}
|
||||
|
||||
type options struct {
|
||||
defRevLimit int
|
||||
concurrentReconciles int
|
||||
ignoreDefNoCtrlReq bool
|
||||
controllerVersion string
|
||||
}
|
||||
|
||||
// Reconcile is the main logic for TraitDefinition controller
|
||||
@@ -67,6 +74,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
if !r.matchControllerRequirement(&traitdefinition) {
|
||||
klog.InfoS("skip traitDefinition: not match the controller requirement of traitDefinition", "traitDefinition", klog.KObj(&traitdefinition))
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// this is a placeholder for finalizer here in the future
|
||||
if traitdefinition.DeletionTimestamp != nil {
|
||||
klog.InfoS("The TraitDefinition is being deleted", "traitDefinition", klog.KRef(req.Namespace, req.Name))
|
||||
@@ -193,12 +205,32 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// Setup adds a controller that reconciles TraitDefinition.
|
||||
func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
}
|
||||
|
||||
func parseOptions(args oamctrl.Args) options {
|
||||
return options{
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
ignoreDefNoCtrlReq: args.IgnoreDefinitionWithoutControllerRequirement,
|
||||
controllerVersion: version.VelaVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reconciler) matchControllerRequirement(traitDefinition *v1beta1.TraitDefinition) bool {
|
||||
if traitDefinition.Annotations != nil {
|
||||
if requireVersion, ok := traitDefinition.Annotations[oam.AnnotationControllerRequirement]; ok {
|
||||
return requireVersion == r.controllerVersion
|
||||
}
|
||||
}
|
||||
if r.ignoreDefNoCtrlReq {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -90,11 +90,13 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r = Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
defRevLimit: defRevisionLimit,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
var ctx context.Context
|
||||
|
||||
@@ -90,11 +90,13 @@ var _ = BeforeSuite(func(done Done) {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r = Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
defRevLimit: defRevisionLimit,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: dm,
|
||||
pd: pd,
|
||||
options: options{
|
||||
defRevLimit: defRevisionLimit,
|
||||
},
|
||||
}
|
||||
Expect(r.SetupWithManager(mgr)).ToNot(HaveOccurred())
|
||||
var ctx context.Context
|
||||
|
||||
@@ -42,17 +42,24 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// Reconciler reconciles a WorkflowStepDefinition object
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
dm discoverymapper.DiscoveryMapper
|
||||
pd *packages.PackageDiscover
|
||||
Scheme *runtime.Scheme
|
||||
record event.Recorder
|
||||
options
|
||||
}
|
||||
|
||||
type options struct {
|
||||
defRevLimit int
|
||||
concurrentReconciles int
|
||||
ignoreDefNoCtrlReq bool
|
||||
controllerVersion string
|
||||
}
|
||||
|
||||
// Reconcile is the main logic for WorkflowStepDefinition controller
|
||||
@@ -68,6 +75,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
if !r.matchControllerRequirement(&wfstepdefinition) {
|
||||
klog.InfoS("skip workflowStepDefinition: not match the controller requirement of workflowStepDefinition", "workflowStepDefinition", klog.KObj(&wfstepdefinition))
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// this is a placeholder for finalizer here in the future
|
||||
if wfstepdefinition.DeletionTimestamp != nil {
|
||||
return ctrl.Result{}, nil
|
||||
@@ -192,11 +204,32 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// Setup adds a controller that reconciles WorkflowStepDefinition.
|
||||
func Setup(mgr ctrl.Manager, args oamctrl.Args) error {
|
||||
r := Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
dm: args.DiscoveryMapper,
|
||||
pd: args.PackageDiscover,
|
||||
options: parseOptions(args),
|
||||
}
|
||||
return r.SetupWithManager(mgr)
|
||||
}
|
||||
|
||||
func parseOptions(args oamctrl.Args) options {
|
||||
return options{
|
||||
defRevLimit: args.DefRevisionLimit,
|
||||
concurrentReconciles: args.ConcurrentReconciles,
|
||||
ignoreDefNoCtrlReq: args.IgnoreDefinitionWithoutControllerRequirement,
|
||||
controllerVersion: version.VelaVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reconciler) matchControllerRequirement(wfstepdefinition *v1beta1.WorkflowStepDefinition) bool {
|
||||
if wfstepdefinition.Annotations != nil {
|
||||
if requireVersion, ok := wfstepdefinition.Annotations[oam.AnnotationControllerRequirement]; ok {
|
||||
return requireVersion == r.controllerVersion
|
||||
}
|
||||
}
|
||||
if r.ignoreDefNoCtrlReq {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
"cuelang.org/go/cue/build"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/pkg/errors"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
"gopkg.in/src-d/go-git.v4"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -215,25 +215,26 @@ func GetOpenAPISchemaFromTerraformComponentDefinition(configuration string) ([]b
|
||||
|
||||
// GetTerraformConfigurationFromRemote gets Terraform Configuration(HCL)
|
||||
func GetTerraformConfigurationFromRemote(name, remoteURL, remotePath string) (string, error) {
|
||||
tmpPath := filepath.Join("./tmp/terraform", name)
|
||||
// Check if the directory exists. If yes, remove it.
|
||||
if _, err := os.Stat(tmpPath); err == nil {
|
||||
err := os.RemoveAll(tmpPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to remove the directory")
|
||||
}
|
||||
}
|
||||
_, err := git.PlainClone(tmpPath, false, &git.CloneOptions{
|
||||
URL: remoteURL,
|
||||
Progress: nil,
|
||||
})
|
||||
userHome, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cachePath := filepath.Join(userHome, ".vela", "terraform", name)
|
||||
// Check if the directory exists. If yes, remove it.
|
||||
entities, err := os.ReadDir(cachePath)
|
||||
if err != nil || len(entities) == 0 {
|
||||
fmt.Printf("loading terraform module %s into %s from %s\n", name, cachePath, remoteURL)
|
||||
if _, err = git.PlainClone(cachePath, false, &git.CloneOptions{
|
||||
URL: remoteURL,
|
||||
Progress: os.Stdout,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
tfPath := filepath.Join(tmpPath, remotePath, "variables.tf")
|
||||
tfPath := filepath.Join(cachePath, remotePath, "variables.tf")
|
||||
if _, err := os.Stat(tfPath); err != nil {
|
||||
tfPath = filepath.Join(tmpPath, remotePath, "main.tf")
|
||||
tfPath = filepath.Join(cachePath, remotePath, "main.tf")
|
||||
if _, err := os.Stat(tfPath); err != nil {
|
||||
return "", errors.Wrap(err, "failed to find main.tf or variables.tf in Terraform configurations of the remote repository")
|
||||
}
|
||||
@@ -242,10 +243,6 @@ func GetTerraformConfigurationFromRemote(name, remoteURL, remotePath string) (st
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to read Terraform configuration")
|
||||
}
|
||||
if err := os.RemoveAll(tmpPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(conf), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,24 +17,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/agiledragon/gomonkey/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/src-d/go-git.v4"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestGetTerraformConfigurationFromRemote(t *testing.T) {
|
||||
// If you hit a panic on macOS as below, please fix it by referencing https://github.com/eisenxp/macos-golink-wrapper.
|
||||
// panic: permission denied [recovered]
|
||||
// panic: permission denied
|
||||
type want struct {
|
||||
config string
|
||||
errMsg string
|
||||
@@ -46,8 +38,6 @@ func TestGetTerraformConfigurationFromRemote(t *testing.T) {
|
||||
path string
|
||||
data []byte
|
||||
variableFile string
|
||||
// mockWorkingPath will create `/tmp/terraform`
|
||||
mockWorkingPath bool
|
||||
}
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
@@ -57,7 +47,7 @@ func TestGetTerraformConfigurationFromRemote(t *testing.T) {
|
||||
args: args{
|
||||
name: "valid",
|
||||
url: "https://github.com/kubevela-contrib/terraform-modules.git",
|
||||
path: "",
|
||||
path: "unittest/",
|
||||
data: []byte(`
|
||||
variable "aaa" {
|
||||
type = list(object({
|
||||
@@ -85,7 +75,7 @@ variable "aaa" {
|
||||
args: args{
|
||||
name: "aws-subnet",
|
||||
url: "https://github.com/kubevela-contrib/terraform-modules.git",
|
||||
path: "aws/subnet",
|
||||
path: "unittest/aws/subnet",
|
||||
data: []byte(`
|
||||
variable "aaa" {
|
||||
type = list(object({
|
||||
@@ -109,47 +99,20 @@ variable "aaa" {
|
||||
}`,
|
||||
},
|
||||
},
|
||||
"working path exists": {
|
||||
args: args{
|
||||
variableFile: "main.tf",
|
||||
mockWorkingPath: true,
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to remove the directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.args.mockWorkingPath {
|
||||
err := os.MkdirAll("./tmp/terraform", 0755)
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll("./tmp/terraform")
|
||||
patch1 := ApplyFunc(os.Remove, func(_ string) error {
|
||||
return errors.New("failed")
|
||||
})
|
||||
defer patch1.Reset()
|
||||
patch2 := ApplyFunc(os.Open, func(_ string) (*os.File, error) {
|
||||
return nil, errors.New("failed")
|
||||
})
|
||||
defer patch2.Reset()
|
||||
}
|
||||
|
||||
patch := ApplyFunc(git.PlainCloneContext, func(ctx context.Context, path string, isBare bool, o *git.CloneOptions) (*git.Repository, error) {
|
||||
var tmpPath string
|
||||
if tc.args.path != "" {
|
||||
tmpPath = filepath.Join("./tmp/terraform", tc.args.name, tc.args.path)
|
||||
} else {
|
||||
tmpPath = filepath.Join("./tmp/terraform", tc.args.name)
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
path := filepath.Join(home, ".vela", "terraform")
|
||||
tmpPath := filepath.Join(path, tc.args.name, tc.args.path)
|
||||
if len(tc.args.data) > 0 {
|
||||
err := os.MkdirAll(tmpPath, os.ModePerm)
|
||||
assert.NilError(t, err)
|
||||
err = ioutil.WriteFile(filepath.Clean(filepath.Join(tmpPath, tc.args.variableFile)), tc.args.data, 0644)
|
||||
assert.NilError(t, err)
|
||||
return nil, nil
|
||||
})
|
||||
defer patch.Reset()
|
||||
}
|
||||
defer os.RemoveAll(tmpPath)
|
||||
|
||||
conf, err := GetTerraformConfigurationFromRemote(tc.args.name, tc.args.url, tc.args.path)
|
||||
if tc.want.errMsg != "" {
|
||||
@@ -62,8 +62,8 @@ const (
|
||||
// AbstractEngine defines Definition's Render interface
|
||||
type AbstractEngine interface {
|
||||
Complete(ctx process.Context, abstractTemplate string, params interface{}) error
|
||||
HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error)
|
||||
Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error)
|
||||
HealthCheck(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, healthPolicyTemplate string) (bool, error)
|
||||
Status(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, customStatusTemplate string, parameter interface{}) (string, error)
|
||||
}
|
||||
|
||||
type def struct {
|
||||
@@ -151,7 +151,7 @@ func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string, pa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) {
|
||||
func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
|
||||
|
||||
var root = initRoot(ctx.BaseContextLabels())
|
||||
var commonLabels = GetCommonLabels(ctx.BaseContextLabels())
|
||||
@@ -162,7 +162,7 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader
|
||||
return nil, err
|
||||
}
|
||||
// workload main resource will have a unique label("app.oam.dev/resourceType"="WORKLOAD") in per component/app level
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), componentWorkload, cli, ns, util.MergeMapOverrideWithDst(map[string]string{
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), componentWorkload, cli, accessor.For(componentWorkload), util.MergeMapOverrideWithDst(map[string]string{
|
||||
oam.LabelOAMResourceType: oam.ResourceTypeWorkload,
|
||||
}, commonLabels), "")
|
||||
if err != nil {
|
||||
@@ -182,7 +182,7 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader
|
||||
return nil, err
|
||||
}
|
||||
// AuxiliaryWorkload will have a unique label("trait.oam.dev/resource"="name of outputs") in per component/app level
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, ns, util.MergeMapOverrideWithDst(map[string]string{
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, accessor.For(componentWorkload), util.MergeMapOverrideWithDst(map[string]string{
|
||||
oam.TraitTypeLabel: AuxiliaryWorkload,
|
||||
}, commonLabels), assist.Name)
|
||||
if err != nil {
|
||||
@@ -197,11 +197,11 @@ func (wd *workloadDef) getTemplateContext(ctx process.Context, cli client.Reader
|
||||
}
|
||||
|
||||
// HealthCheck address health check for workload
|
||||
func (wd *workloadDef) HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error) {
|
||||
func (wd *workloadDef) HealthCheck(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, healthPolicyTemplate string) (bool, error) {
|
||||
if healthPolicyTemplate == "" {
|
||||
return true, nil
|
||||
}
|
||||
templateContext, err := wd.getTemplateContext(ctx, cli, ns)
|
||||
templateContext, err := wd.getTemplateContext(ctx, cli, accessor)
|
||||
if err != nil {
|
||||
return false, errors.WithMessage(err, "get template context")
|
||||
}
|
||||
@@ -228,11 +228,11 @@ func checkHealth(templateContext map[string]interface{}, healthPolicyTemplate st
|
||||
}
|
||||
|
||||
// Status get workload status by customStatusTemplate
|
||||
func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error) {
|
||||
func (wd *workloadDef) Status(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, customStatusTemplate string, parameter interface{}) (string, error) {
|
||||
if customStatusTemplate == "" {
|
||||
return "", nil
|
||||
}
|
||||
templateContext, err := wd.getTemplateContext(ctx, cli, ns)
|
||||
templateContext, err := wd.getTemplateContext(ctx, cli, accessor)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get template context")
|
||||
}
|
||||
@@ -417,7 +417,7 @@ func initRoot(contextLabels map[string]string) map[string]interface{} {
|
||||
return root
|
||||
}
|
||||
|
||||
func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, ns string) (map[string]interface{}, error) {
|
||||
func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, accessor util.NamespaceAccessor) (map[string]interface{}, error) {
|
||||
var root = initRoot(ctx.BaseContextLabels())
|
||||
var commonLabels = GetCommonLabels(ctx.BaseContextLabels())
|
||||
|
||||
@@ -431,7 +431,7 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, ns, util.MergeMapOverrideWithDst(map[string]string{
|
||||
object, err := getResourceFromObj(ctx.GetCtx(), traitRef, cli, accessor.For(traitRef), util.MergeMapOverrideWithDst(map[string]string{
|
||||
oam.TraitTypeLabel: assist.Type,
|
||||
}, commonLabels), assist.Name)
|
||||
if err != nil {
|
||||
@@ -446,11 +446,11 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n
|
||||
}
|
||||
|
||||
// Status get trait status by customStatusTemplate
|
||||
func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error) {
|
||||
func (td *traitDef) Status(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, customStatusTemplate string, parameter interface{}) (string, error) {
|
||||
if customStatusTemplate == "" {
|
||||
return "", nil
|
||||
}
|
||||
templateContext, err := td.getTemplateContext(ctx, cli, ns)
|
||||
templateContext, err := td.getTemplateContext(ctx, cli, accessor)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get template context")
|
||||
}
|
||||
@@ -458,11 +458,11 @@ func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, cu
|
||||
}
|
||||
|
||||
// HealthCheck address health check for trait
|
||||
func (td *traitDef) HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error) {
|
||||
func (td *traitDef) HealthCheck(ctx process.Context, cli client.Client, accessor util.NamespaceAccessor, healthPolicyTemplate string) (bool, error) {
|
||||
if healthPolicyTemplate == "" {
|
||||
return true, nil
|
||||
}
|
||||
templateContext, err := td.getTemplateContext(ctx, cli, ns)
|
||||
templateContext, err := td.getTemplateContext(ctx, cli, accessor)
|
||||
if err != nil {
|
||||
return false, errors.WithMessage(err, "get template context")
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ const (
|
||||
ContextComponents = "components"
|
||||
// ContextComponentType is the component type of current trait binding with
|
||||
ContextComponentType = "componentType"
|
||||
// ComponentRevisionPlaceHolder is the component revision name placeHolder, this field will be replace with real value
|
||||
// ComponentRevisionPlaceHolder is the component revision name placeHolder, this field will be replaced with real value
|
||||
// after component be created
|
||||
ComponentRevisionPlaceHolder = "KUBEVELA_COMPONENT_REVISION_PLACEHOLDER"
|
||||
// ContextDataArtifacts is used to store unstructured resources of components
|
||||
|
||||
@@ -302,7 +302,7 @@ func strategyUnify(baseFile *ast.File, patchFile *ast.File, params *UnifyParams,
|
||||
|
||||
ret := baseInst.Value().Unify(patchInst.Value())
|
||||
|
||||
rv, err := toString(ret)
|
||||
rv, err := toString(ret, removeTmpVar)
|
||||
if err != nil {
|
||||
return rv, errors.WithMessage(err, " format result toString")
|
||||
}
|
||||
@@ -358,7 +358,11 @@ func jsonMergePatch(base cue.Value, patch cue.Value) (string, error) {
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to merge base value and patch value by JsonMergePatch")
|
||||
}
|
||||
return string(merged), nil
|
||||
output, err := OpenBaiscLit(string(merged))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse open basic lit for merged result")
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func jsonPatch(base cue.Value, patch cue.Value) (string, error) {
|
||||
@@ -379,5 +383,9 @@ func jsonPatch(base cue.Value, patch cue.Value) (string, error) {
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to apply json patch")
|
||||
}
|
||||
return string(merged), nil
|
||||
output, err := OpenBaiscLit(string(merged))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse open basic lit for merged result")
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -358,3 +358,38 @@ func listOpen(expr ast.Node) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeTmpVar(expr ast.Node) ast.Node {
|
||||
switch v := expr.(type) {
|
||||
case *ast.File:
|
||||
for _, decl := range v.Decls {
|
||||
removeTmpVar(decl)
|
||||
}
|
||||
case *ast.Field:
|
||||
removeTmpVar(v.Value)
|
||||
case *ast.StructLit:
|
||||
var elts []ast.Decl
|
||||
for _, elt := range v.Elts {
|
||||
if field, isField := elt.(*ast.Field); isField {
|
||||
if ident, isIdent := field.Label.(*ast.Ident); isIdent && strings.HasPrefix(ident.Name, "_") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
removeTmpVar(elt)
|
||||
elts = append(elts, elt)
|
||||
}
|
||||
v.Elts = elts
|
||||
case *ast.BinaryExpr:
|
||||
removeTmpVar(v.X)
|
||||
removeTmpVar(v.Y)
|
||||
case *ast.EmbedDecl:
|
||||
removeTmpVar(v.Expr)
|
||||
case *ast.Comprehension:
|
||||
removeTmpVar(v.Value)
|
||||
case *ast.ListLit:
|
||||
for _, elt := range v.Elts {
|
||||
removeTmpVar(elt)
|
||||
}
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"cuelang.org/go/cue/ast"
|
||||
"cuelang.org/go/cue/parser"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
@@ -128,3 +129,33 @@ func TestWalk(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveTmpVar(t *testing.T) {
|
||||
src := `spec: {
|
||||
_tmp: "x"
|
||||
list: [{
|
||||
_tmp: "x"
|
||||
retain: "y"
|
||||
}, {
|
||||
_tmp: "x"
|
||||
retain: "z"
|
||||
}]
|
||||
retain: "y"
|
||||
}
|
||||
`
|
||||
r := require.New(t)
|
||||
var runtime cue.Runtime
|
||||
inst, err := runtime.Compile("-", src)
|
||||
r.NoError(err)
|
||||
s, err := toString(inst.Value(), removeTmpVar)
|
||||
r.NoError(err)
|
||||
r.Equal(`spec: {
|
||||
list: [{
|
||||
retain: "y"
|
||||
}, {
|
||||
retain: "z"
|
||||
}]
|
||||
retain: "y"
|
||||
}
|
||||
`, s)
|
||||
}
|
||||
|
||||
@@ -718,10 +718,10 @@ func TestImports(t *testing.T) {
|
||||
context: stepSessionID: "3w9qkdgn5w"`
|
||||
v, err := NewValue(`
|
||||
import (
|
||||
"vela/op"
|
||||
"vela/custom"
|
||||
)
|
||||
|
||||
id: op.context.stepSessionID
|
||||
id: custom.context.stepSessionID
|
||||
|
||||
`+cont, nil, cont)
|
||||
assert.NilError(t, err)
|
||||
|
||||
@@ -302,9 +302,6 @@ func (ctx *templateContext) GetCtx() context.Context {
|
||||
}
|
||||
|
||||
func (ctx *templateContext) SetCtx(newContext context.Context) {
|
||||
if ctx.ctx != nil {
|
||||
return
|
||||
}
|
||||
ctx.ctx = newContext
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
"github.com/oam-dev/cluster-register/pkg/hub"
|
||||
"github.com/oam-dev/cluster-register/pkg/spoke"
|
||||
"github.com/pkg/errors"
|
||||
@@ -36,11 +39,7 @@ import (
|
||||
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1"
|
||||
clustercommon "github.com/oam-dev/cluster-gateway/pkg/common"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
@@ -49,6 +48,7 @@ import (
|
||||
|
||||
// KubeClusterConfig info for cluster management
|
||||
type KubeClusterConfig struct {
|
||||
FilePath string
|
||||
ClusterName string
|
||||
CreateNamespace string
|
||||
*clientcmdapi.Config
|
||||
@@ -84,17 +84,26 @@ func (clusterConfig *KubeClusterConfig) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterByVelaSecret create cluster secrets for KubeVela to use
|
||||
func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error {
|
||||
if err := ensureClusterNotExists(ctx, cli, clusterConfig.ClusterName); err != nil {
|
||||
return errors.Wrapf(err, "cannot use cluster name %s", clusterConfig.ClusterName)
|
||||
// PostRegistration try to create namespace after cluster registered. If failed, cluster will be unregistered.
|
||||
func (clusterConfig *KubeClusterConfig) PostRegistration(ctx context.Context, cli client.Client) error {
|
||||
if clusterConfig.CreateNamespace == "" {
|
||||
return nil
|
||||
}
|
||||
if err := ensureNamespaceExists(ctx, cli, clusterConfig.ClusterName, clusterConfig.CreateNamespace); err != nil {
|
||||
_ = DetachCluster(ctx, cli, clusterConfig.ClusterName, DetachClusterManagedClusterKubeConfigPathOption(clusterConfig.FilePath))
|
||||
return fmt.Errorf("failed to ensure %s namespace installed in cluster %s: %w", clusterConfig.CreateNamespace, clusterConfig.ClusterName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (clusterConfig *KubeClusterConfig) createClusterSecret(ctx context.Context, cli client.Client, withEndpoint bool) error {
|
||||
var credentialType clusterv1alpha1.CredentialType
|
||||
data := map[string][]byte{
|
||||
"endpoint": []byte(clusterConfig.Cluster.Server),
|
||||
}
|
||||
if !clusterConfig.Cluster.InsecureSkipTLSVerify {
|
||||
data["ca.crt"] = clusterConfig.Cluster.CertificateAuthorityData
|
||||
data := map[string][]byte{}
|
||||
if withEndpoint {
|
||||
data["endpoint"] = []byte(clusterConfig.Cluster.Server)
|
||||
if !clusterConfig.Cluster.InsecureSkipTLSVerify {
|
||||
data["ca.crt"] = clusterConfig.Cluster.CertificateAuthorityData
|
||||
}
|
||||
}
|
||||
if len(clusterConfig.AuthInfo.Token) > 0 {
|
||||
credentialType = clusterv1alpha1.CredentialTypeServiceAccountToken
|
||||
@@ -115,22 +124,50 @@ func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: data,
|
||||
}
|
||||
if err := cli.Create(ctx, secret); err != nil {
|
||||
return cli.Create(ctx, secret)
|
||||
}
|
||||
|
||||
// RegisterByVelaSecret create cluster secrets for KubeVela to use
|
||||
func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error {
|
||||
if err := ensureClusterNotExists(ctx, cli, clusterConfig.ClusterName); err != nil {
|
||||
return errors.Wrapf(err, "cannot use cluster name %s", clusterConfig.ClusterName)
|
||||
}
|
||||
if err := clusterConfig.createClusterSecret(ctx, cli, true); err != nil {
|
||||
return errors.Wrapf(err, "failed to add cluster to kubernetes")
|
||||
}
|
||||
// TODO(somefive): create namespace now only work for cluster secret
|
||||
if clusterConfig.CreateNamespace != "" {
|
||||
if err := ensureNamespaceExists(ctx, cli, clusterConfig.ClusterName, clusterConfig.CreateNamespace); err != nil {
|
||||
_ = cli.Delete(ctx, secret)
|
||||
return errors.Wrapf(err, "failed to ensure %s namespace installed in cluster %s", clusterConfig.CreateNamespace, clusterConfig.ClusterName)
|
||||
return clusterConfig.PostRegistration(ctx, cli)
|
||||
}
|
||||
|
||||
// CreateBootstrapConfigMapIfNotExists alternative to
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.24.1/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go#L43
|
||||
func CreateBootstrapConfigMapIfNotExists(ctx context.Context, cli client.Client) error {
|
||||
cm := &corev1.ConfigMap{}
|
||||
key := apitypes.NamespacedName{Namespace: metav1.NamespacePublic, Name: "cluster-info"}
|
||||
if err := cli.Get(ctx, key, cm); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
cm.ObjectMeta = metav1.ObjectMeta{Namespace: key.Namespace, Name: key.Name}
|
||||
adminConfig, err := clientcmd.NewDefaultPathOptions().GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster
|
||||
bs, err := clientcmd.Write(clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{"": adminConfig.Clusters[adminCluster]},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{"kubeconfig": string(bs)}
|
||||
return cli.Create(ctx, cm)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterClusterManagedByOCM create ocm managed cluster for use
|
||||
// TODO(somefive): OCM ManagedCluster only support cli join now
|
||||
func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.Context, args *JoinClusterArgs) error {
|
||||
func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.Context, cli client.Client, args *JoinClusterArgs) error {
|
||||
newTrackingSpinner := args.trackingSpinnerFactory
|
||||
hubCluster, err := hub.NewHubCluster(args.hubConfig)
|
||||
if err != nil {
|
||||
@@ -164,6 +201,9 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
return errors.Wrap(err, "fail to convert spoke-cluster kubeconfig")
|
||||
}
|
||||
|
||||
if err = CreateBootstrapConfigMapIfNotExists(ctx, cli); err != nil {
|
||||
return fmt.Errorf("failed to ensure cluster-info ConfigMap in kube-public namespace exists: %w", err)
|
||||
}
|
||||
spokeTracker := newTrackingSpinner("Building registration config for the managed cluster")
|
||||
spokeTracker.FinalMSG = "Successfully prepared registration config.\n"
|
||||
spokeTracker.Start()
|
||||
@@ -172,6 +212,7 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
args.ioStreams.Infof("Using the api endpoint from hub kubeconfig %q as registration entry.\n", args.hubConfig.Host)
|
||||
overridingRegistrationEndpoint = args.hubConfig.Host
|
||||
}
|
||||
|
||||
hubKubeToken, err := hubCluster.GenerateHubClusterKubeConfig(ctx, overridingRegistrationEndpoint)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fail to generate the token for spoke-cluster")
|
||||
@@ -230,7 +271,7 @@ func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.
|
||||
|
||||
// LoadKubeClusterConfigFromFile create KubeClusterConfig from kubeconfig file
|
||||
func LoadKubeClusterConfigFromFile(filepath string) (*KubeClusterConfig, error) {
|
||||
clusterConfig := &KubeClusterConfig{}
|
||||
clusterConfig := &KubeClusterConfig{FilePath: filepath}
|
||||
var err error
|
||||
clusterConfig.Config, err = clientcmd.LoadFromFile(filepath)
|
||||
if err != nil {
|
||||
@@ -345,7 +386,7 @@ func JoinClusterByKubeConfig(ctx context.Context, cli client.Client, kubeconfigP
|
||||
return nil, errors.Wrapf(err, "failed to determine the registration endpoint for the hub cluster "+
|
||||
"when parsing --in-cluster-bootstrap flag")
|
||||
}
|
||||
if err = clusterConfig.RegisterClusterManagedByOCM(ctx, args); err != nil {
|
||||
if err = clusterConfig.RegisterClusterManagedByOCM(ctx, cli, args); err != nil {
|
||||
return clusterConfig, err
|
||||
}
|
||||
}
|
||||
@@ -384,12 +425,12 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
if clusterName == ClusterLocalName {
|
||||
return ErrReservedLocalClusterName
|
||||
}
|
||||
vc, err := GetVirtualCluster(ctx, cli, clusterName)
|
||||
vc, err := prismclusterv1alpha1.NewClusterClient(cli).Get(ctx, clusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch vc.Type {
|
||||
switch vc.Spec.CredentialType {
|
||||
case clusterv1alpha1.CredentialTypeX509Certificate, clusterv1alpha1.CredentialTypeServiceAccountToken:
|
||||
clusterSecret, err := getMutableClusterSecret(ctx, cli, clusterName)
|
||||
if err != nil {
|
||||
@@ -398,7 +439,7 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
if err := cli.Delete(ctx, clusterSecret); err != nil {
|
||||
return errors.Wrapf(err, "failed to detach cluster %s", clusterName)
|
||||
}
|
||||
case types.CredentialTypeOCMManagedCluster:
|
||||
case prismclusterv1alpha1.CredentialTypeOCMManagedCluster:
|
||||
if args.managedClusterKubeConfigPath == "" {
|
||||
return errors.New("kubeconfig-path must be set to detach ocm managed cluster")
|
||||
}
|
||||
@@ -416,7 +457,7 @@ func DetachCluster(ctx context.Context, cli client.Client, clusterName string, o
|
||||
return err
|
||||
}
|
||||
managedCluster := ocmclusterv1.ManagedCluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName}}
|
||||
if err = cli.Delete(context.Background(), &managedCluster); err != nil {
|
||||
if err = cli.Delete(ctx, &managedCluster); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -98,6 +98,9 @@ const (
|
||||
LabelProject = "core.oam.dev/project"
|
||||
|
||||
LabelResourceRules = "rules.oam.dev/resources"
|
||||
|
||||
// LabelControllerName indicates the controller name
|
||||
LabelControllerName = "controller.oam.dev/name"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -198,7 +201,7 @@ const (
|
||||
// AnnotationWorkloadName indicates the managed workload's name by trait
|
||||
AnnotationWorkloadName = "trait.oam.dev/workload-name"
|
||||
|
||||
// AnnotationControllerRequirement indicates the controller version that can process the application.
|
||||
// AnnotationControllerRequirement indicates the controller version that can process the application/definition.
|
||||
AnnotationControllerRequirement = "app.oam.dev/controller-version-require"
|
||||
|
||||
// AnnotationApplicationServiceAccountName indicates the name of the ServiceAccount to use to apply Components and run Workflow.
|
||||
|
||||
@@ -953,3 +953,38 @@ func AsController(r *corev1.ObjectReference) metav1.OwnerReference {
|
||||
ref.Controller = &c
|
||||
return ref
|
||||
}
|
||||
|
||||
// NamespaceAccessor namespace accessor for resource
|
||||
type NamespaceAccessor interface {
|
||||
For(obj client.Object) string
|
||||
Namespace() string
|
||||
}
|
||||
|
||||
type applicationResourceNamespaceAccessor struct {
|
||||
applicationNamespace string
|
||||
overrideNamespace string
|
||||
}
|
||||
|
||||
// For access namespace for resource
|
||||
func (accessor *applicationResourceNamespaceAccessor) For(obj client.Object) string {
|
||||
if accessor.overrideNamespace != "" {
|
||||
return accessor.overrideNamespace
|
||||
}
|
||||
if originalNamespace := obj.GetNamespace(); originalNamespace != "" {
|
||||
return originalNamespace
|
||||
}
|
||||
return accessor.applicationNamespace
|
||||
}
|
||||
|
||||
// Namespace the namespace by default
|
||||
func (accessor *applicationResourceNamespaceAccessor) Namespace() string {
|
||||
if accessor.overrideNamespace != "" {
|
||||
return accessor.overrideNamespace
|
||||
}
|
||||
return accessor.applicationNamespace
|
||||
}
|
||||
|
||||
// NewApplicationResourceNamespaceAccessor create namespace accessor for resource in application
|
||||
func NewApplicationResourceNamespaceAccessor(appNs, overrideNs string) NamespaceAccessor {
|
||||
return &applicationResourceNamespaceAccessor{applicationNamespace: appNs, overrideNamespace: overrideNs}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ limitations under the License.
|
||||
|
||||
package oam
|
||||
|
||||
// SystemDefinitonNamespace golbal value for controller and webhook systemlevel namespace
|
||||
var (
|
||||
// SystemDefinitonNamespace golbal value for controller and webhook systemlevel namespace
|
||||
SystemDefinitonNamespace string = "vela-system"
|
||||
|
||||
// ApplicationControllerName means the controller is application
|
||||
ApplicationControllerName string = "vela-core"
|
||||
)
|
||||
|
||||
@@ -81,3 +81,14 @@ func TestParseApplyOncePolicy(t *testing.T) {
|
||||
r.NoError(err)
|
||||
r.Equal(policySpec, spec)
|
||||
}
|
||||
|
||||
func TestParsePolicy(t *testing.T) {
|
||||
r := require.New(t)
|
||||
// Test skipping empty policy
|
||||
app := &v1beta1.Application{Spec: v1beta1.ApplicationSpec{
|
||||
Policies: []v1beta1.AppPolicy{{Type: "example", Name: "s", Properties: nil}},
|
||||
}}
|
||||
exists, err := parsePolicy(app, "example", nil)
|
||||
r.False(exists, "empty policy should not be included")
|
||||
r.NoError(err)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
// GetEnvBindingPolicy extract env-binding policy with given policy name, if policy name is empty, the first env-binding policy will be used
|
||||
func GetEnvBindingPolicy(app *v1beta1.Application, policyName string) (*v1alpha1.EnvBindingSpec, error) {
|
||||
for _, policy := range app.Spec.Policies {
|
||||
if (policy.Name == policyName || policyName == "") && policy.Type == v1alpha1.EnvBindingPolicyType {
|
||||
if (policy.Name == policyName || policyName == "") && policy.Type == v1alpha1.EnvBindingPolicyType && policy.Properties != nil {
|
||||
envBindingSpec := &v1alpha1.EnvBindingSpec{}
|
||||
err := json.Unmarshal(policy.Properties.Raw, envBindingSpec)
|
||||
return envBindingSpec, err
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user